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移動: ダッシュボード → Webhook → Webhookを追加
- 2入力: エンドポイントURL — HTTPSで公開アクセス可能である必要あり
- 3選択: 受信したいイベント
- 4コピー: 署名シークレット — 一度しか表示されないので安全に保管
- 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を更新。