Webhook

Webhook

プロジェクトで認証イベントが発生したときにリアルタイムHTTP通知を受信。ユーザーが登録、サインイン、またはバンされるたびにデータベースに同期したり、ウェルカムメールを送ったり、ワークフローをトリガーしたりできます。

Webhookの仕組み

Authonプロジェクトでイベントが発生すると、APIはJSONペイロードとともに登録済みエンドポイントにPOSTリクエストを送信します。各リクエストにはWebhook署名シークレットを使用した生リクエストボディのHMAC-SHA256署名であるAuthon-Signatureヘッダーが含まれます。イベントを処理する前に必ずこの署名を検証してください。

1
Authonでイベント発生(例:ユーザー登録)
2
AuthonがWebhookシークレットでHMAC-SHA256署名
3
AuthonがエンドポイントURLにPOSTリクエスト送信
4
サーバーが署名を検証してイベントを処理
5
サーバーが受信確認のため2xxで応答

Webhookの設定

  1. 1
    移動: ダッシュボード → Webhook → Webhookを追加
  2. 2
    入力: エンドポイントURL — HTTPSで公開アクセス可能である必要あり
  3. 3
    選択: 受信したいイベント
  4. 4
    コピー: 署名シークレット — 一度しか表示されないので安全に保管
  5. 5
    設定: サーバー環境にAUTHON_WEBHOOK_SECRETを追加

イベントタイプ

イベントトリガー
user.created新しいユーザーが登録を完了
user.updatedユーザープロファイルが更新された
user.deletedユーザーアカウントが永久削除された
user.banned管理者がユーザーをバンした
user.unbannedバンされたユーザーが復元された
session.createdユーザーのサインインに成功(新しいセッション)
session.endedユーザーが正常にサインアウト
session.revoked管理者がセッションを強制取り消し
provider.linkedユーザーがOAuthプロバイダーをアカウントに連携
provider.unlinkedユーザーがOAuthプロバイダーの連携を解除

ペイロード形式

すべてのWebhookペイロードは一貫したトップレベル構造を持つJSONオブジェクトです。 data フィールドにイベント固有の詳細が含まれます。

json
{
  "id": "evt_1a2b3c4d5e6f",
  "type": "user.created",
  "projectId": "proj_abc123",
  "timestamp": "2026-01-15T10:30:00.000Z",
  "data": {
    "user": {
      "id": "usr_abc123",
      "email": "user@example.com",
      "displayName": "Jane Doe",
      "emailVerified": false,
      "isBanned": false,
      "publicMetadata": null,
      "createdAt": "2026-01-15T10:30:00.000Z",
      "updatedAt": "2026-01-15T10:30:00.000Z"
    }
  }
}

session.*イベントの場合、 dataフィールドには sessionオブジェクト(id、ipAddress、userAgent)も含まれます。

署名検証

各Webhookリクエストには Authon-Signature 形式の sha256=<hex_digest>. ヘッダーが含まれます。Webhookシークレットを使用して 生リクエストボディ に対する期待HMAC-SHA256を計算し、タイミングセーフ等値比較で確認してください。

検証には常に 生ボディバッファ を使用してください。Expressの json() ミドルウェアはボディを再シリアライズし、空白が変更されて署名が壊れる可能性があります。

Node.jsの例

routes/webhooks.ts
import { createHmac, timingSafeEqual } from "crypto";
import express from "express";

const router = express.Router();

function verifySignature(
  rawBody: Buffer,
  signature: string,
  secret: string,
): boolean {
  const expected = createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  const actual = signature.replace("sha256=", "");

  const expectedBuf = Buffer.from(expected, "hex");
  const actualBuf = Buffer.from(actual, "hex");

  return (
    expectedBuf.length === actualBuf.length &&
    timingSafeEqual(expectedBuf, actualBuf)
  );
}

router.post(
  "/webhooks/authon",
  express.raw({ type: "application/json" }), // raw body required
  (req, res) => {
    const signature = req.headers["authon-signature"] as string;
    const webhookSecret = process.env.AUTHON_WEBHOOK_SECRET!;

    if (!verifySignature(req.body, signature, webhookSecret)) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const event = JSON.parse(req.body.toString()) as {
      id: string;
      type: string;
      projectId: string;
      timestamp: string;
      data: Record<string, unknown>;
    };

    switch (event.type) {
      case "user.created": {
        const { user } = event.data as any;
        // Sync new user to your database
        await db.users.upsert({ authonId: user.id, email: user.email });
        break;
      }
      case "user.updated": {
        const { user } = event.data as any;
        await db.users.update({ authonId: user.id }, { displayName: user.displayName });
        break;
      }
      case "user.deleted": {
        const { user } = event.data as any;
        await db.users.delete({ authonId: user.id });
        break;
      }
      case "user.banned": {
        const { user } = event.data as any;
        await db.users.update({ authonId: user.id }, { suspended: true });
        break;
      }
      case "session.created": {
        const { user, session } = event.data as any;
        console.log(`${user.email} signed in from ${session.ipAddress}`);
        break;
      }
    }

    res.status(200).json({ received: true });
  }
);

export default router;

SDKの使用

@authon/node SDKはタイミングセーフ比較を処理する組み込み検証ヘルパーを提供します:

ts
import { AuthonBackend } from "@authon/node";

const authon = new AuthonBackend(process.env.AUTHON_SECRET_KEY!);

router.post(
  "/webhooks/authon",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.headers["authon-signature"] as string;

    let event: Record<string, unknown>;
    try {
      event = authon.webhooks.verify(
        req.body,
        signature,
        process.env.AUTHON_WEBHOOK_SECRET!,
      );
    } catch {
      return res.status(401).json({ error: "Invalid signature" });
    }

    // event is the parsed and verified JSON payload
    console.log("Verified event:", event.type);
    res.json({ received: true });
  }
);

リトライポリシー

エンドポイントが非2xxステータスコードを返すか 10秒以内に応答しない場合、Authonは指数バックオフで配信を再試行します。5回失敗後、イベントは失敗とマークされダッシュボードから手動で再試行できます。

試行遅延累積時間
初回0s
1回目リトライ5 seconds~5s
2回目リトライ30 seconds~35s
3回目リトライ5 minutes~6m
4回目リトライ30 minutes~36m
5回目リトライ2 hours~2h 36m

ベストプラクティス

常に署名を検証
署名検証をスキップしないでください。誰でもエンドポイントにPOSTできます — 検証はリクエストがAuthonからであることを証明します。
生ボディパースを使用
署名検証にはリクエストボディの正確な生バイトが必要です。Webhookルートでは JSONミドルウェアを避けてください。
素早く応答、非同期処理
即座に200を返し、イベントを非同期で処理してください。エンドポイントが10秒以上かかる場合、Authonはリトライします。
ハンドラーをべき等に
リトライにより同じイベントを複数回受信することがあります。イベントidで重複排除してください — 処理済みIDをDBに保存。
定期的にシークレットを更新
90日ごとにダッシュボードから新しい署名シークレットを生成。ダウンタイムなしでデプロイのAUTHON_WEBHOOK_SECRETを更新。
Authon — ユニバーサル認証プラットフォーム