Webhook Design
Webhooks are untrusted HTTP with delayed, duplicated, and reordered delivery. Authenticate the sender, make handlers idempotent, and never fetch attacker-controlled URLs without SSRF controls.
For persistent sockets, use websocket-design. For public HTTP APIs, use api-design.
Workflow
- Direction: inbound (you receive) vs outbound (you send) vs both.
- Event catalog: type, version, payload schema, PII, ordering needs.
- Auth: HMAC over the raw body + timestamp (or mTLS / public-key). Reject stale signatures (clock skew window, e.g. 5 minutes).
- Idempotency: event id as the dedupe key; store processed ids; safe to retry.
- Retries: exponential backoff, give-up budget, dead-letter, and what "success" means (2xx only).
- Receiver rules: verify first, parse second; constant-time compare; no work before auth.
- Sender rules: timeout, follow-redirects off by default, SSRF allow list if user-supplied URLs, secret rotation with dual-key overlap.
Output format
## Webhook design: <integration>
**Direction:** inbound | outbound | both
**Events:** …
### Signing
…
### Receiver contract
status codes, idempotency, time limit
### Retry / DLQ
…
### Versioning
…
### Abuse cases
replay, SSRF, secret leak, poison payload
Rules
- Sign the raw body, not a re-serialized JSON object.
- Include a timestamp and reject old signatures (replay).
- Deduplicate on a unique event id, not on "looks the same".
- Outbound delivery to customer URLs is an SSRF problem: block link- local / metadata / private ranges unless explicitly required.
- Secrets rotate with two valid keys during overlap. Never log the signing secret or full payload if it contains PII/tokens.
- Version events (
type+api_versionor schema id). Additive fields first; breaking changes get a new type. - Do not use GET for delivery. POST (or the platform's documented method) with a bounded body size.
Edge cases
- At-least-once is the default. Design for duplicates; do not promise exactly-once unless a transactional outbox is real.
- Fan-out storms: one user action → N hooks; budget and coalesce.
- Signature schemes you did not invent: follow Stripe/GitHub/Slack docs when integrating their platform; do not "improve" them.
- Private network receivers: prefer allowlisted egress and mTLS over "shared secret in a query param".
---