Last month I lost an entire weekend to a Stripe webhook that worked locally but silently failed in production. Duplicate events from retries, signature mismatches from parsing JSON instead of the raw body, and idempotency gaps that almost double-charged a customer.
Sound familiar? You're not alone. Stripe delivers webhooks at least once — meaning duplicates are guaranteed, not edge cases.
The Problem
These are the failure modes I kept seeing on r/SaaS and Hacker News:
- Retry storms — Stripe retries failed webhooks, your handler runs twice, customer gets double-provisioned
- Signature verification fails in prod — because you parsed JSON before verifying (must use raw body)
-
Test/live mode mix-ups —
sk_test_keys verifyinglivemode: trueevents - Timeout issues — your handler takes >30s, Stripe retries, chaos ensues
The Solution: HookReplay
I built HookReplay — a free, browser-based webhook debugger. No signup. No data leaves your machine.
What it does
- Paste any webhook JSON → instant field inspection (event ID, type, amount, status)
- HMAC-SHA256 signature verifier — uses raw body, not parsed JSON (the #1 mistake)
- Flags 8 common failure modes — retry storms, idempotency gaps, test/live mix-ups, timeout issues
- 100% client-side — runs entirely in your browser
How Idempotency Actually Works
The fix for duplicate events is dead simple but often skipped:
// PostgreSQL + Node.js pattern
const { rows } = await db.query(
'INSERT INTO processed_events (event_id) VALUES ($1) ON CONFLICT DO NOTHING RETURNING event_id',
[event.id]
);
if (rows.length === 0) return res.status(200).send('Already processed');
// Safe to process event
Use the Stripe event.id as your idempotency key. Store it in a table with a unique constraint. Check before processing. Done.
Try It Free
👉 HookReplay — Free Webhook Debugger
There's also a $19 Pro Playbook with production-ready idempotency middleware templates (Node.js, Python, PostgreSQL) and a pre-deploy checklist.
What's Your Worst Webhook Story?
Drop a comment — I'm collecting failure modes to add to the tool's detector. The more specific, the better.
Built by an indie dev who got burned so you don't have to.
Top comments (0)