Skip to content
API Design Learn/Production REST I: Performance & Reliability
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Webhooks

8 min read

You'll learn to

  • -Design a webhook delivery system with HMAC signature verification so receivers can trust the payload's origin
  • -Handle webhook delivery failures with retries, and design events to be safely re-deliverable

A webhook inverts the usual request direction: instead of a client polling an API for updates, the API pushes an event to a URL the client registered in advance, the moment something happens. This is efficient (no wasted polling) but introduces real design problems polling never had - authenticity, delivery guarantees, and ordering.

HMAC Signing: Proving the Payload Is Genuine

A webhook receiver has no built-in way to know a request claiming to be from your API actually came from you - anyone who discovers the endpoint URL could send a forged payload. The fix is signing each payload with a shared secret (established when the webhook was registered), so the receiver can verify authenticity before trusting the payload at all.

The delivery, with a verifiable signature
POST https://client-app.example.com/webhooks/orders
X-Webhook-Signature: sha256=7d38cdd689735...
Content-Type: application/json

{"event": "order.shipped", "order_id": 501, "shipped_at": "2026-08-07T10:00:00Z"}
What the receiver does to verify the signature
import hmac
import hashlib

def verify_webhook_signature(payload_body: bytes, received_signature: str, shared_secret: str) -> bool:
    expected = "sha256=" + hmac.new(
        shared_secret.encode(), payload_body, hashlib.sha256
    ).hexdigest()
    # constant-time comparison - avoids leaking info via response-time differences
    return hmac.compare_digest(expected, received_signature)

The receiver recomputes the same HMAC over the raw payload bytes using the shared secret, and compares it to the signature the sender included - if they match, the payload genuinely came from someone who knows the shared secret (presumably the real API), and hasn't been tampered with in transit. The comparison itself needs to be constant-time (`hmac.compare_digest`, not `==`) to avoid leaking timing information an attacker could exploit to guess the correct signature byte by byte.

Retries and Idempotent Receivers

A receiver's endpoint might be temporarily down, slow, or return an error - a webhook sender needs a retry policy (typically exponential backoff over some bounded window) rather than firing once and giving up. But retries mean the receiver might get the same event delivered more than once, which means every webhook event needs a unique, stable event ID the receiver can use to detect and safely ignore a duplicate delivery - the same idempotency-key discipline from earlier in this course, applied to inbound events instead of outbound requests.

Webhooks do not guarantee ordering by default - two events fired close together can arrive at the receiver out of order, especially across retries. If ordering genuinely matters, include a sequence number or timestamp in the payload so the receiver can detect and correctly handle out-of-order delivery, rather than assuming arrival order matches event order.

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

Ready to Build This?

Design The Webhook Wire in the API Design Lab's Production Patterns act.

ScaleDojo Logo
Initializing ScaleDojo