웹훅 검증
이 문서는 영문 원고를 AI로 번역한 내용이라 표현이 어색할 수 있습니다.
Sume는 모든 웹훅 전달에 <timestamp>.<raw_body>에 대한 HMAC-SHA256 서명을
붙여 sume-v1=<hex> 형태로 보냅니다. verifyWebhook이 그 확인을 대신하므로
직접 구현하지 않아도 됩니다.
서명 시크릿은 대시보드의 웹훅 탭(/dashboard/webhooks — 표시 후 복사)에서
직접 확인하거나, account:read 범위를 가진 API 키로
GET /v1/webhooks/signing-secret을 호출해 받을 수 있습니다. 워크스페이스별로
파생된 값이므로, 서명이 유효하다는 것은 공용 시크릿을 가진 누군가가 아니라
여러분에게 서명되었다는 뜻입니다. 로컬 샘플이 Sume 워커의 서명과 맞도록 환경변수
이름은 SUME_COM_WEBHOOK_SIGNING_SECRET을 쓰고, API 키와 같은 수준으로
보관하세요. API 키와는 다른 값이며 클라이언트와도 무관합니다.
verifyWebhook은 client를 받지 않고 요청도 보내지 않습니다.
대시보드에는 시크릿의 지문도 함께 표시되고, 모든 전송에
x-sume-webhook-secret-fingerprint 헤더로 같은 값이 실립니다. 서명 검증이 계속
실패하면 지문을 비교하세요. 이 과정에서 티켓에 붙여넣어도 안전한 값은 지문뿐입니다.
시크릿 교체
시크릿이 유출되었을 수 있다면 교체하세요. 대시보드의 웹훅 → 시크릿 교체,
또는 account:write 범위를 가진 키로 POST /v1/webhooks/signing-secret/rotate를
호출하면 됩니다.
교체는 즉시 전환이 아닙니다. 이후 24시간 동안 Sume는 모든 전송을 두 시크릿
모두로 서명하고, x-sume-webhook-signature 헤더에 새 서명부터 순서대로 쉼표로
이어 보냅니다.
둘 중 하나만 갖고 있어도 검증되므로, 버튼을 누른 순간에 맞춰 배포할 필요 없이
편한 일정으로 수신 서버를 교체하면 됩니다. 이 기간이 지나면 이전 시크릿은 더 이상
검증되지 않습니다. 기간이 열려 있는 동안 대시보드에 만료 시각이 표시되고, 두 API
응답 모두 rotation.previous_valid_until로 같은 값을 전달합니다.
x-sume-webhook-secret-fingerprint는 교체한 순간부터 이 기간 중에도 새 시크릿을
가리킵니다. 지금 어떤 시크릿이 함께 허용되는지가 아니라, 어떤 시크릿으로 옮겨가야
하는지를 알려 주는 값입니다. 한 기간 안에서 두 번 교체하면 두 단계 전 시크릿은 즉시
폐기되며, 유출이 실제로 멈추는 지점이 바로 이 동작입니다.
입력
| 필드 | 설명 |
|---|---|
body | 원본 본문입니다. string, ArrayBuffer, 또는 타입 배열입니다. |
headers | Headers, Map, 또는 일반 객체(Node의 req.headers)입니다. 대소문자를 구분하지 않습니다. |
secret | Sume 웹훅 서명 시크릿입니다. |
toleranceSeconds | 재전송 허용 시간입니다. 기본값은 300입니다. 0이면 타임스탬프 확인을 건너뜁니다. |
읽는 헤더는 두 개입니다.
동작을 좌우하는 네 가지 규칙
- 원본 본문을 넘기세요. 파싱했다가 다시 직렬화한 객체는 검증되지 않습니다.
키 순서와 공백도 서명 대상의 일부입니다. JSON을 대신 파싱해 주는 프레임워크는
이미 바이트를 망가뜨린 상태입니다. Express에서는 웹훅 라우트에만
express.raw({ type: "application/json" })을 붙이세요. Next.js App Router에서는 무엇보다 먼저await request.text()를 호출하세요. async입니다. 구현이node:crypto대신 WebCrypto를 쓰기 때문에 Workers, Deno, 그리고node:스펙파이어를 거부하는 번들러에서도 패키지를 import할 수 있습니다.await하세요.- 잘못된 전달에는 throw하지 않고
false를 반환합니다. 헤더 누락, 엉뚱한 타임스탬프, 잘못된 서명은 모두 그냥 검증 실패입니다. 분기할 지점은 하나이고try/catch는 필요 없습니다. - 비교는 상수 시간으로 이뤄지고, 재전송 허용 시간은 HMAC을 계산하기 전에 먼저 확인합니다.
검증기 하나, 표면 둘
Run 웹훅(*.run.terminal, run_id 포함)과 생성 Job 웹훅(job.*, job_id
포함)은 sume-v1 스킴을 그대로 공유합니다. 페이로드는 다르지만 서명은
같습니다. 그래서 검증기 하나로 둘 다 처리할 수 있습니다. event로
분기하고, 본문에 run_id가 있다고 가정하지 마세요.
알 수 없는 이벤트를 204로 처리하는 것이, 새로 추가된 이벤트 타입이 500과
재시도 폭주로 번지는 것을 막아 줍니다.
필요하다면 쓰는 상수
verifyWebhook이 일반적인 경우를 처리합니다. 수신기를 직접 통제하지 못할
때 — 예를 들어 게이트웨이가 코드 실행 전에 검증하는 경우 — 를 위해 구성
요소도 내보냅니다.
| Export | 값 |
|---|---|
SUME_WEBHOOK_SIGNATURE_VERSION | "sume-v1" |
SUME_WEBHOOK_SIGNATURE_HEADER | "x-sume-webhook-signature" |
SUME_WEBHOOK_TIMESTAMP_HEADER | "x-sume-webhook-timestamp" |
DEFAULT_SUME_WEBHOOK_TOLERANCE_SECONDS | 300 |