DEV Community

Syncoders
Syncoders

Posted on

11 things your Stripe webhook handler has to survive (25-second clip)

Stripe delivers webhooks at least once, out of order, and sometimes late. Most billing bugs (double emails, a cancelled user getting Pro back, a lost payment) come from those three facts. This short walks through the 11 situations we test a handler against:

Here they are in plain words, with the one-line fix for each:

  1. Duplicate delivery. The same event arrives twice. Store event.id and skip any id you've already processed.
  2. Out of order. subscription.updated can land before subscription.created. Don't trust arrival order; re-read the object from Stripe or compare timestamps before you overwrite state.
  3. Replayed old request. Someone resends a captured request. Signature verification checks the timestamp, so keep the default tolerance.
  4. Tampered body. Verify against the raw request bytes, not re-serialised JSON, or a perfectly valid signature will fail. A changed body must be rejected.
  5. Wrong secret. Test mode, live mode and the Stripe CLI each have their own signing secret. A mismatch should fail loudly with a 400, not silently.
  6. Guest checkout with a null customer_email. Read the email from customer_details and handle the null case.
  7. Async payment. A checkout can complete unpaid, then checkout.session.async_payment_succeeded arrives later, then gets redelivered. Grant access on the payment event, once.
  8. Async payment failed. Handle async_payment_failed so the order doesn't hang in "pending".
  9. Failed renewal. invoice.payment_failed should start your dunning flow, not cut access on the first try.
  10. Refund. Decide what a refund does to access, and make it idempotent too.
  11. Dispute. charge.dispute.created is worth an alert to a human.

The longer write-up, with diagrams and code for each failure, is here: 7 ways Stripe webhooks break in production. There's also a free Stripe webhooks go-live checklist.

Disclosure: the clip is for our paid Stripe Webhook Survival Pack ($9). It replays these 11 scenarios offline with real Stripe-Signature headers, no Stripe account needed: TypeScript (52 tests) and Python (41 tests), plus a 31-point checklist, a 9-page chapter and a "review my handler" AI prompt.

Top comments (0)