콘텐츠로 건너뛰기

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입니다. 본문에는 type, data, 최상위 메타데이터 헤더가 포함됩니다.

{
  "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')
}

재시도 및 dead-letter

  • 전송 실패 시 지수 백오프로 재시도합니다. 최대 재시도 횟수 이후 메시지는 수동 검사를 위해 D1의 dead-letter 저장소에 기록됩니다.
  • 전송은 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" }'

이벤트 API

push webhook 외에도 XID는 GET /v1/events에서 cursor 페이지네이션을 갖춘 순서 있는 불변 이벤트 스트림을 제공합니다. webhook 재시도 사이에 이벤트를 놓치지 않고 신뢰할 수 있는 동기화를 구축하려면 이 스트림을 폴링하세요.

curl 'https://xid.dev/v1/events?limit=100&after=evt_xxx' \
  -H 'Authorization: Bearer sk_live_xxx'
탐색

입력하여 검색...

화살표 키로 이동Enter 키로 선택Escape 키로 닫기