---
title: "Webhook"
description: "XID 이벤트를 구독하고 사용자, 세션, 조직이 변경될 때 서명된 HTTP 페이로드를 수신합니다."
locale: "ko"
---

> Documentation Index
> Fetch the locale documentation index at: https://xid.dev/ko/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook

## 이벤트 명명 규칙

이벤트는 `<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`, 최상위 메타데이터 헤더가 포함됩니다.

```json
{
  "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 값입니다. |

```js
// 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`를 가집니다.

```mermaid
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` 값을 가지지만 원본 `type`과 `data`는 보존됩니다.

```shell
# 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 재시도 사이에 이벤트를 놓치지 않고 신뢰할 수 있는 동기화를 구축하려면 이 스트림을 폴링하세요.

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

Source: https://xid.dev/ko/webhooks/index.mdx
