웹훅 검증

이 문서는 영문 원고를 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 키와는 다른 값이며 클라이언트와도 무관합니다. verifyWebhookclient를 받지 않고 요청도 보내지 않습니다.

대시보드에는 시크릿의 지문도 함께 표시되고, 모든 전송에 x-sume-webhook-secret-fingerprint 헤더로 같은 값이 실립니다. 서명 검증이 계속 실패하면 지문을 비교하세요. 이 과정에서 티켓에 붙여넣어도 안전한 값은 지문뿐입니다.

시크릿 교체

시크릿이 유출되었을 수 있다면 교체하세요. 대시보드의 웹훅 → 시크릿 교체, 또는 account:write 범위를 가진 키로 POST /v1/webhooks/signing-secret/rotate를 호출하면 됩니다.

교체는 즉시 전환이 아닙니다. 이후 24시간 동안 Sume는 모든 전송을 두 시크릿 모두로 서명하고, x-sume-webhook-signature 헤더에 새 서명부터 순서대로 쉼표로 이어 보냅니다.

둘 중 하나만 갖고 있어도 검증되므로, 버튼을 누른 순간에 맞춰 배포할 필요 없이 편한 일정으로 수신 서버를 교체하면 됩니다. 이 기간이 지나면 이전 시크릿은 더 이상 검증되지 않습니다. 기간이 열려 있는 동안 대시보드에 만료 시각이 표시되고, 두 API 응답 모두 rotation.previous_valid_until로 같은 값을 전달합니다.

<Callout type="warn"> `verifyWebhook`이 다중 서명 헤더를 처리하는 것은 **`@sume-com/sdk` 0.5.0** 부터입니다. 그보다 낮은 버전이나 헤더를 문자열 일치로 비교하는 자체 구현은 이 기간 동안 모든 전송에서 실패합니다. 교체하기 **전에** 수신 서버를 먼저 올려 주세요. 기간이 아닐 때는 서명이 하나만 실리므로, 교체하지 않는 분들에게는 아무 변화가 없습니다. </Callout>

x-sume-webhook-secret-fingerprint는 교체한 순간부터 이 기간 중에도 시크릿을 가리킵니다. 지금 어떤 시크릿이 함께 허용되는지가 아니라, 어떤 시크릿으로 옮겨가야 하는지를 알려 주는 값입니다. 한 기간 안에서 두 번 교체하면 두 단계 전 시크릿은 즉시 폐기되며, 유출이 실제로 멈추는 지점이 바로 이 동작입니다.

입력

필드설명
body원본 본문입니다. string, ArrayBuffer, 또는 타입 배열입니다.
headersHeaders, Map, 또는 일반 객체(Node의 req.headers)입니다. 대소문자를 구분하지 않습니다.
secretSume 웹훅 서명 시크릿입니다.
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_SECONDS300

다음

  • Run 웹훅 — 전달, 이벤트, 페이로드, 재시도, 그리고 JavaScript가 아닌 수신기를 위한 원본 스킴입니다
  • 웹훅 — 다른 표면인 생성 Job 웹훅입니다
  • 실행 기다리기 — 전달이 아직 꺼져 있을 때 쓰는 폴링 경로입니다