Design an idempotent webhook verifier

Design the receiving side of webhooks the Stripe way: HMAC-SHA256 over the timestamp and the raw body, a tolerance window against replays, constant-time comparison, two active secrets during rotation, and idempotent processing with leased claims so retries and concurrent deliveries do the work exactly once.

● Medium🔒 PRODesign problem·23 min read·Intermediate → Advanced
webhook signatureStripe-SignatureHMACidempotency keywebhook retriessecret rotation

01Understand the problem

✓What you will learn
  • Verify HMAC-SHA256 over `timestamp.rawBody` and know why the raw bytes matter
  • Use a tolerance window (Stripe's libraries default to 300 s) against replay
  • Make processing idempotent with event ids, leased claims and a unique constraint
  • Handle retries and out-of-order delivery without corrupting state
  • Rotate secrets with two active secrets and multiple v1 signatures
◎Interview radar

What they probe: what exactly is signed, why a JSON body parser breaks verification, how replays are bounded, what happens when the same event arrives twice at once, and how rotation works without dropping events. Where candidates slip: verifying re-serialised JSON, === on signatures, no timestamp check, marking an event done before the side effect commits, assuming events arrive in order.

A webhook is an HTTP POST a provider sends to your endpoint when something happens (an invoice is paid). Anyone can POST to that URL, so the provider signs each delivery with a shared endpoint secret. Stripe sends a Stripe-Signature header such as t=1492774577,v1=5257a869...: t is a Unix timestamp and v1 is the hex HMAC-SHA256 of the string t + "." + raw body under the secret. Providers deliver at least once and retry failures (Stripe retries for up to three days in live mode), so duplicates are normal.

  1. Verify the signature over the exact raw body bytes before parsing JSON.
  2. Reject timestamps more than 300 s from now in either direction.
  3. Accept a signature from any currently active secret.
  4. Process each event id at most once to completion; a failure must allow a retry.
  5. Return 2xx quickly; slow work goes to a queue.

This is an advanced module

Problems: replay & revocation components is part of the Pro pass. 3 more sections — the deep dives, the final design, follow-ups and the test — unlock with it.

Already a cyart.io or studio.cyart.io member? Your pass code is in your Studio account.