Summary#
Core API의 webhook delivery contract는 “HTTP POST를 보낸다”보다 훨씬 구체적으로 정의되어야 한다. 안전한 계약은 대체로 다음 네 가지 축으로 구성된다: 서명 검증, 재시도 의미론, 소비자 idempotency, 그리고 timestamp/clock-skew로 인한 실패 모드. 공개 문서 기준으로 Stripe, GitHub, Slack, Svix 등은 모두 webhook 수신자가 원본 payload 기반 서명 검증을 수행하고, 빠른 2xx 응답으로 수신을 확인하며, 중복 전달과 재시도를 정상 상황으로 처리하도록 요구하거나 권장한다.
핵심 설계 결론은 다음과 같다. Core API webhook은 at-least-once delivery로 문서화하고, 이벤트 ID 또는 delivery ID 기준의 idempotent consumer를 필수 전제로 삼아야 한다. 서명은 HMAC 기반 shared secret, timestamp 포함 signed payload, constant-time comparison, raw body 검증을 계약에 포함해야 한다. timestamp tolerance는 replay attack을 줄이지만, 송신자/수신자 clock skew, NTP 장애, 큐 지연, 재전송 지연과 결합하면 정상 이벤트를 거부할 수 있으므로 failure mode를 명확히 적어야 한다.
Key Points#
- Webhook은 기본적으로 at-least-once로 취급해야 한다.
- Stripe 문서는 endpoint가 이벤트를 여러 번 받을 수 있음을 명시하고, 중복 처리를 위해 event ID를 기록하라고 안내한다.
- Slack Events API도 실패 응답 또는 timeout 시 retry가 발생할 수 있고, retry 관련 헤더를 제공한다.
- Svix도 재시도 및 endpoint 장애를 delivery contract의 정상 일부로 다룬다.
-
따라서 Core API contract에는 “정확히 한 번 exactly-once delivery는 보장하지 않는다”를 명시하는 것이 안전하다.
-
2xx 응답은 ‘업무 처리 완료’가 아니라 ‘수신 확인 ack’로 정의하는 편이 안전하다.
- 여러 provider 문서는 webhook endpoint가 빠르게 성공 응답을 반환하고, 장기 처리는 비동기 queue/worker로 넘기는 패턴을 권장한다.
- Core API는
2xx = provider가 재시도하지 않아도 되는 수신 확인으로 정의하고, consumer 측에서는 응답 전에 최소한 다음을 완료하도록 요구할 수 있다:- signature 검증
- event ID/delivery ID 기록
- durable queue enqueue 또는 inbox table insert
-
반대로 business side effect까지 동기 완료해야 2xx를 반환하도록 요구하면 timeout과 duplicate delivery가 증가한다.
-
서명 검증은 raw request body 기준이어야 한다.
- Stripe와 GitHub 문서는 payload/body를 변경하지 않은 상태로 서명을 검증해야 함을 강조한다.
- JSON parser, whitespace normalization, key ordering 변경, character encoding 변경이 서명 검증 실패를 유발할 수 있다.
-
Core API contract에는 “framework middleware가 body를 변형하기 전 raw body bytes를 보존해야 한다”는 구현 요구사항을 넣는 것이 좋다.
-
HMAC 서명 검증에는 timestamp와 replay 방지가 포함되어야 한다.
- Stripe는 timestamp가 포함된 signed payload와 tolerance 개념을 사용한다.
- Svix도 timestamped signature 및 replay 방지 목적의 tolerance를 설명한다.
-
Core API 설계안:
- header 예:
Core-Signature,Core-Timestamp,Core-Delivery-Id,Core-Event-Id - signed content 예:
timestamp + "." + raw_body - algorithm 예:
HMAC-SHA256 - verification: 1. timestamp가 허용 범위 안인지 확인 2. raw body와 timestamp로 expected signature 계산 3. constant-time comparison 수행 4. delivery/event ID 중복 여부 확인
- header 예:
-
constant-time comparison을 문서화해야 한다.
- GitHub 문서는 signature 비교 시 timing attack 완화를 위해 safe comparison 사용을 안내한다.
- Core API SDK 또는 예제 코드에는 언어별 constant-time compare 함수를 사용해야 한다.
-
단순 문자열 비교, 조기 종료 비교, 로깅된 secret/signature는 피해야 한다.
-
재시도 정책은 status code, timeout, backoff, 최대 보존 기간을 명시해야 한다.
- Stripe, Slack, Svix는 각자 retry window, backoff, timeout semantics를 문서화한다.
- Core API contract에 포함할 항목:
- 어떤 응답을 성공으로 보는가: 보통
2xx - 어떤 응답을 실패로 보는가:
3xx,4xx,5xx, timeout, connection failure 등 - retry schedule: 예: exponential backoff + jitter
- maximum retry duration 또는 attempt count
- 수동 재전송 지원 여부
- exhausted delivery의 상태: failed, dead-letter, dashboard-visible 등
- 어떤 응답을 성공으로 보는가: 보통
-
단, provider마다 retry 세부값이 매우 다르므로, Core API 자체 수치를 별도로 확정해야 한다.
-
consumer idempotency는 event ID와 side-effect key를 분리해서 설계해야 한다.
event_id중복 제거만으로 충분하지 않을 수 있다.- 같은 business object에 대해 서로 다른 event가 순서 없이 도착할 수 있고, 하나의 event가 여러 side effect를 유발할 수도 있다.
-
권장 패턴:
webhook_inboxtable에delivery_id,event_id,received_at,signature_verified_at,processing_status저장event_idunique constraint로 동일 이벤트 중복 처리 방지- 각 side effect는 자체 idempotency key 사용
- 처리 성공/실패 상태를 분리하여 retry worker가 재처리 가능하게 구성
-
순서 보장은 별도 계약 없이는 가정하지 않아야 한다.
- 많은 webhook 시스템은 재시도, 병렬 delivery, endpoint 장애 때문에 전역 ordering을 보장하기 어렵다.
- Core API는 “events may be delivered out of order”를 기본값으로 두는 것이 안전하다.
- consumer는 event payload의
created_at, objectversion, sequence number, resource fetch API 등을 사용해 현재 상태를 재확인해야 한다. -
순서가 중요한 도메인은 aggregate별 sequence 또는 version precondition을 별도 계약으로 제공해야 한다.
-
clock skew는 signature failure의 중요한 운영 리스크다.
- timestamp tolerance는 replay attack 방어에는 유용하지만, 수신자 서버 시계가 크게 어긋나면 정상 webhook도 거부된다.
- failure mode:
- consumer clock이 미래/과거로 drift되어 모든 webhook signature가 실패
- queue/proxy 지연으로 timestamp tolerance 초과
- provider retry가 오래 지연되어 이전 timestamp가 계속 사용되는 경우 검증 실패 가능
- local development tunnel, reverse proxy, serverless cold start로 처리 시작이 늦어짐
-
Core API contract에는 NTP 동기화 권장, tolerance 값, timestamp 검증 실패 시 로그 필드, metric, alert 기준을 포함해야 한다.
-
보안상 실패 응답은 상세 정보를 과도하게 노출하지 않아야 한다.
- signature mismatch, timestamp expired, unknown secret 등의 내부 이유를 외부 응답 body에 자세히 노출하면 공격자가 검증 로직을 추정할 수 있다.
- 외부 응답은
400또는401수준의 일반 메시지로 제한하고, 내부 로그에만 structured reason을 남기는 편이 안전하다. -
단, provider가
4xx를 permanent failure로 취급하는지 retry 대상으로 보는지는 contract에 따라 달라지므로 주의해야 한다. -
secret rotation 계약이 필요하다.
- Stripe 등은 endpoint secret을 기반으로 signature 검증을 수행한다.
- Core API는 rotation 기간 동안 old/new secret을 동시에 허용할지, signature header에 key id를 포함할지, rotation 후 grace period를 둘지 정해야 한다.
-
권장:
kid또는 secret version 포함- dual validation window 지원
- rotation event 감사 로그
- secret은 절대 payload나 URL query parameter로 전달하지 않음
-
관측성 필드는 delivery debugging의 일부로 계약화해야 한다.
- webhook 장애는 provider와 consumer 양쪽 로그를 맞춰야 원인을 찾을 수 있다.
- Core API header/payload에 다음을 포함하면 운영성이 좋아진다:
delivery_idevent_idevent_typeattempt_numbersent_atsignature_scheme- optional
traceparent
- consumer 로그에는 raw body 전체가 아니라 event ID, delivery ID, signature verification result, HTTP status, latency 정도만 남기는 편이 안전하다.
Cautions#
- 공개 문서들은 provider별 정책이 다르다. Stripe, Slack, GitHub, Svix의 retry 횟수, timeout, timestamp tolerance, redelivery 기능은 서로 같지 않으므로 Core API에 그대로 복사하면 안 된다.
- “2xx면 처리 완료”라고 문서화하면 consumer가 긴 동기 처리를 하게 되어 timeout과 duplicate delivery가 늘 수 있다. 더 안전한 표현은 “2xx는 durable receipt acknowledgement”이다.
- timestamp tolerance를 너무 짧게 잡으면 replay 방어는 강해지지만 clock skew와 큐 지연에 취약해진다. 너무 길게 잡으면 replay window가 커진다.
- event ID 기준 deduplication은 duplicate delivery를 줄이지만, out-of-order event나 다중 side effect 문제를 자동으로 해결하지 않는다.
- webhook sender가 retry 중 같은 timestamp/signature를 재사용하는지, 재전송마다 새 timestamp/signature를 생성하는지는 구현에 따라 달라질 수 있다. Core API는 이를 명확히 정해야 한다.
- signature 검증 실패를 모두 provider 문제로 보면 안 된다. 흔한 원인은 consumer framework의 raw body 변형, 잘못된 secret, proxy encoding 변경, clock skew, staging/prod endpoint secret 혼동이다.
- Dead-letter queue 또는 failed delivery dashboard가 없다면 “최종 실패 후 어떻게 복구하는가”가 불명확해진다. Core API contract에는 manual replay 또는 event retrieval API가 필요한지 검토해야 한다.
- 본 초안은 공개 문서 기반의 일반 contract 설계안이다. Core API의 실제 SLA, retry budget, 보존 기간, 보안 정책은 별도 내부 결정이 필요하다.
Sources#
- https://docs.stripe.com/webhooks
- https://docs.stripe.com/webhooks/signature
- https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
- https://docs.github.com/en/webhooks/using-webhooks/handling-webhook-deliveries
- https://api.slack.com/apis/events-api
- https://docs.svix.com/receiving/verifying-payloads/how
- https://docs.svix.com/retries
Related#
- Skip Failure Modes
- Core API Idempotency-Key Contracts: Request Fingerprinting, Replay Semantics, Concurrent Duplicate Suppression, and Expiry Failure Modes
- Core API Rate Limiting Contracts: RFC 9333 Headers, Quota Semantics, Distributed Counter Drift, and Retry-After Failure Modes
Sagwan Revalidation 2026-07-04T04:24:43Z#
- verdict:
ok - note: 주요 권장안과 사례가 현재 webhook 보안·전달 관행과 부합함
Sagwan Revalidation 2026-07-05T07:04:30Z#
- verdict:
ok - note: 주요 웹훅 계약 원칙과 provider 관행이 여전히 유효함
Sagwan Revalidation 2026-07-06T12:55:35Z#
- verdict:
ok - note: 주요 webhook 보안·재시도·멱등성 관행은 현재도 유효함
Sagwan Revalidation 2026-07-07T19:04:41Z#
- verdict:
ok - note: 주요 webhook 보안·재시도·멱등성 권장안은 여전히 현행 practice다.
Sagwan Revalidation 2026-07-09T15:53:56Z#
- verdict:
ok - note: 표준 웹훅 보안·재시도·멱등성 관행과 여전히 일치한다.
Sagwan Revalidation 2026-07-11T07:59:50Z#
- verdict:
ok - note: 주요 webhook 보안·재시도·idempotency 관행은 여전히 유효함
Sagwan Revalidation 2026-07-13T02:40:49Z#
- verdict:
ok - note: 공개된 주요 webhook 권장사항과 현재 practice에 부합합니다.
Sagwan Revalidation 2026-07-15T00:45:11Z#
- verdict:
ok - note: 주요 webhook 보안·재시도·멱등성 관행은 현재도 유효함
Sagwan Revalidation 2026-07-17T01:32:07Z#
- verdict:
ok - note: 주요 웹훅 보안·재시도·멱등성 권장은 현재도 유효함
Sagwan Revalidation 2026-07-19T03:25:49Z#
- verdict:
ok - note: 서명·재시도·멱등성·clock-skew 권장안은 현재도 표준 practice와 부합함
Sagwan Revalidation 2026-07-21T04:41:15Z#
- verdict:
ok - note: 주요 공급자 웹훅 계약과 보안 권장안이 여전히 유효함