본문으로 건너뛰기
문서 · 웹훅

웹훅

Usher는 환경에서 일어나는 일을 실시간으로 서버에 알려 줄 수 있습니다. 누군가 가입하거나, 로그인하거나, 정보를 바꾸거나, 탈퇴할 때입니다. 각 이벤트는 지정한 주소로 보내는 서명된 POST 요청이며, 서버가 응답하지 않으면 다시 시도합니다.

엔드포인트 추가하기

Usher Console에서 환경을 열고 웹훅 → 새 엔드포인트로 이동하세요. 소유한 https:// 주소를 입력하고, 받을 이벤트를 선택합니다. Console은 엔드포인트의 서명 시크릿(whsec_…)을 한 번만 보여 줍니다. 서버의 다른 시크릿과 함께 보관하세요.

이벤트발생 시점
user.created누군가 계정을 만들었을 때
user.updated계정이 바뀌었을 때: 이메일, 이름, 확인 상태 또는 차단(SCIM을 통한 조직 디렉터리의 차단 포함)
user.deleted계정이 삭제되었을 때
session.created누군가 로그인했을 때
session.ended세션이 끝났을 때: 로그아웃, 폐기 또는 조기 만료

테스트 보내기를 누르면 구독한 이벤트와 관계없이 그 엔드포인트에만 webhook.test 이벤트를 보냅니다.

전달되는 내용

요청
POST /usher/webhooks HTTP/1.1
content-type: application/json
user-agent: Usher-Webhooks/1
webhook-id: msg_2fk7x…
webhook-timestamp: 1793643116
webhook-signature: v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE=
본문
{
  "type": "user.created",
  "timestamp": "2026-11-02T18:11:56.412Z",
  "data": {
    "user": {
      "id": "Qz3m…",
      "email": "lena@acme.com",
      "emailVerified": false,
      "name": "Lena Live",
      "image": null,
      "banned": false,
      "createdAt": "2026-11-02T18:11:56.380Z",
      "updatedAt": "2026-11-02T18:11:56.380Z"
    }
  }
}

이벤트는 사용자와 세션을 설명할 뿐, 시크릿은 절대 담지 않습니다. 토큰, 암호 해시, 역할, 차단 사유는 포함되지 않습니다.

모든 전달 확인하기

Usher는 Standard Webhooks 사양에 따라 서명하므로, 이 사양의 어떤 라이브러리로든 서명을 확인할 수 있습니다. 검증되지 않는 요청은 모두 거부하세요. 라이브러리는 타임스탬프가 5분 넘게 차이 나는 요청도 거부하므로, 오래된 전달을 재전송할 수 없습니다.

app/usher/webhooks/route.ts
// a route that receives Usher's webhooks (Node, the standardwebhooks package)
import { Webhook } from 'standardwebhooks'

const wh = new Webhook(process.env.USHER_WEBHOOK_SECRET!) // whsec_…, from Usher Console

export async function POST(request: Request) {
  const body = await request.text() // the raw body: the signature covers it byte for byte
  let event: { type: string; timestamp: string; data: Record<string, unknown> }
  try {
    event = wh.verify(body, Object.fromEntries(request.headers)) as typeof event
  } catch {
    return new Response('bad signature', { status: 400 })
  }
  // webhook-id is the same on every attempt: skip one you have handled already
  if (await alreadyHandled(request.headers.get('webhook-id')!)) return new Response(null, { status: 204 })
  await queue(event) // answer quickly; do the work afterwards
  return new Response(null, { status: 204 })
}
  • 15초 이내에 아무 2xx 코드로 응답하세요. 리디렉션을 포함해 그 밖의 응답은 모두 실패로 간주됩니다.
  • 중복을 예상하세요. 메시지는 모든 시도에서 같은 webhook-id를 유지합니다. 이미 처리한 메시지는 건너뛰세요.
  • 순서에 의존하지 마세요. 재시도된 메시지가 더 최근 이벤트보다 늦게 도착할 수 있습니다.

서버가 응답하지 않을 때

Usher는 5초, 5분, 30분, 2시간, 5시간, 10시간, 10시간 뒤에 다시 시도하고(총 약 하루 반), 그다음에는 포기합니다. Console의 전달 로그에서 모든 메시지의 시도 기록과 서버의 마지막 응답을 확인할 수 있으며, 다시 보내기로 전달을 처음부터 다시 시작할 수 있습니다.

새 시크릿

Console에서 새 시크릿을 누르면 시크릿이 하나 더 만들어지고 한 번만 표시됩니다. 이후 24시간 동안 각 전달에는 시크릿마다 하나씩 두 개의 서명이 담기므로, 아무것도 놓치지 않고 새 시크릿을 배포할 수 있습니다. 그 뒤로는 새 시크릿으로만 서명합니다.

전달이 나가는 곳

웹훅은 Usher의 이그레스 프록시를 거쳐 나가며, 이 프록시는 사설 주소나 내부 주소에 연결하지 않습니다. 엔드포인트는 공개 이름을 쓰는 https:// 주소여야 합니다. IP 주소, 사설 이름, Usher 자체 도메인의 주소는 사용할 수 없습니다.