コンテンツへ移動

webhook

XID イベントをサブスクライブし、ユーザー、セッション、組織が変更されると署名付き HTTP ペイロードを受信します。

Markdown で表示

イベント命名規則

イベントは <object>.<action> パターンに従います。各イベントは安定した type 名、一意の svix-id、ISO 8601 タイムスタンプを持ちます。

オブジェクト 操作
user created, updated, deleted
session created, ended, removed, revoked
organization created, updated, deleted
organizationMembership created, updated, deleted
organizationInvitation created, accepted, revoked
organizationDomain created, updated, deleted, verified, verification_failed
authentication password_succeeded, password_failed, passkey_succeeded, passkey_failed, mfa_succeeded, mfa_failed, oauth_succeeded, oauth_failed, sso_succeeded, sso_failed, magic_auth_succeeded, magic_auth_failed, email_verification_succeeded, email_verification_failed, radar_risk_detected
connection activated, deactivated, deleted, saml_certificate_renewed, renewal_required
dsync activated, deleted, user.created, user.updated, user.deleted, group.created, group.updated, group.deleted, group.user_added, group.user_removed
role created, updated, deleted
permission created, updated, deleted
email created(開発者が送信を引き継ぐ際に発火)
sms created(開発者が送信を引き継ぐ際に発火)
billing subscription.created, subscription.updated, paymentAttempt.succeeded, paymentAttempt.failed

ペイロード構造

すべての webhook 配信は Content-Type: application/json の HTTP POST です。ボディには typedata、トップレベルのメタデータヘッダーが含まれます。

{
  "type": "user.created",
  "data": {
    "id": "usr_01abc",
    "email_addresses": [{ "email_address": "alice@example.com" }],
    "created_at": 1700000000000
  }
}

署名検証

XID はすべての配信を HMAC-SHA256 で署名します。ペイロードを処理する前に署名を検証してください。リプレイ攻撃を防ぐため、5 分以上経過した配信は拒否してください。

ヘッダー 説明
svix-id 一意のメッセージ ID。再試行された配信の重複排除に使用してください。
svix-timestamp メッセージが送信された時刻(Unix 秒)。
svix-signature ${svix-id}.${svix-timestamp}.${raw-body} をエンドポイント署名シークレットで Base64 エンコードした HMAC-SHA256。
// Node.js / Cloudflare Workers example
async function verifyWebhook(request, secret) {
  const svixId = request.headers.get('svix-id')
  const svixTimestamp = request.headers.get('svix-timestamp')
  const svixSignature = request.headers.get('svix-signature')
  const body = await request.text()

  // Reject messages older than 5 minutes
  const ts = Number(svixTimestamp)
  if (Math.abs(Date.now() / 1000 - ts) > 300) {
    throw new Error('webhook timestamp out of tolerance')
  }

  const signedContent = `${svixId}.${svixTimestamp}.${body}`
  const keyData = Uint8Array.from(atob(secret), c => c.charCodeAt(0))
  const key = await crypto.subtle.importKey(
    'raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']
  )
  const msgData = new TextEncoder().encode(signedContent)

  // svix-signature may contain multiple comma-separated values
  for (const sig of svixSignature.split(' ')) {
    const prefix = 'v1,'
    if (!sig.startsWith(prefix)) continue
    const sigBytes = Uint8Array.from(atob(sig.slice(prefix.length)), c => c.charCodeAt(0))
    const valid = await crypto.subtle.verify('HMAC', key, sigBytes, msgData)
    if (valid) return JSON.parse(body)
  }
  throw new Error('invalid webhook signature')
}

再試行とデッドレター

  • 配信失敗は指数バックオフで再試行されます。最大再試行回数を超えると、メッセージは手動確認用に D1 のデッドレターストアに書き込まれます。
  • 配信は Cloudflare Queues を通じて認証パスから切り離されています。エンドポイントが遅かったり使用不能でも、ログインのレイテンシーには影響しません。
  • svix-id ヘッダーを使って受信側で配信を重複排除してください。再試行は元の試行と同じ svix-id を持ちます。
flowchart LR
  XID --> Queue
  Queue -->|HTTPS| Endpoint
  Endpoint -->|2xx| ACK
  Endpoint -->|non-2xx| Retry
  Retry --> Queue
  Retry -->|max_retries| dlq["D1 DLQ"]

手動リプレイ

POST /v1/webhooks/:endpointId/replay を使ってメッセージ ID または時間範囲でイベントをリプレイします。リプレイされたイベントは新しい svix-id 値を持ちますが、元の typedata は保持されます。

# Replay events from the last hour
curl -X POST https://xid.dev/v1/webhooks/whe_xxx/replay \
  -H 'Authorization: Bearer sk_live_xxx' \
  -H 'Content-Type: application/json' \
  -d '{ "since": "2024-01-01T00:00:00Z", "until": "2024-01-01T01:00:00Z" }'

Events API

push webhook に加え、XID は GET /v1/events でカーソルページネーション付きの順序付き不変イベントストリームを公開します。ストリームを取得することで、webhook 再試行の間にイベントを取りこぼすことなく確実な同期を構築できます。

curl 'https://xid.dev/v1/events?limit=100&after=evt_xxx' \
  -H 'Authorization: Bearer sk_live_xxx'
ナビゲーション

入力して検索...

矢印キーで移動Enter キーで選択Escape キーで閉じる