The Webhook Failure Modes Nobody Warns You About
Until you're staring at a 78-hour Stripe retry schedule wondering why your handler never fired.
Webhook integrations look simple. You point Stripe at your endpoint, you get a 200 OK, you're done.
Then something breaks in production — and you have no idea what. Was it the payload? The signature? Your server? Stripe's retry queue? Something downstream?
This isn't a guide to building webhook endpoints. It's a field guide to the failures developers actually hit — and how to debug them faster.
Failure Mode 1: The Silent Drop
Your endpoint returns 200 OK to Stripe. But your handler never fires.
What actually happened: your server accepted the request, but the payload was routed to a different part of your application that silently failed. Or the event was filtered by a middleware. Or the webhook was received but the database write failed and the error was swallowed.
The classic debug move: pepper your handler with console.log(). Deploy. Wait for Stripe to retry (up to 78 hours). Check logs. Repeat.
Faster way: capture the raw webhook before it reaches your server. See exactly what was sent, what your endpoint returned, and when — without touching your application code.
Failure Mode 2: The Empty Payload
Stripe says it sent the event. Your handler receives it. But event.data.object is null or {}.
This is almost always an API version mismatch — your endpoint is using a different Stripe API version than the one that generated the event. Stripe doesn't warn you; it just sends what it has.
The catch: you can't see the raw payload your endpoint received. You only see what your code parsed. And if the parsing failed silently, the debug path starts from the wrong place.
Fix: log the raw request body at the very top of your handler, before any parsing, before any middleware.
Failure Mode 3: The Signature Validation Loop
You're validating the Stripe signature. It keeps failing. You regenerate your webhook secret. It still fails. You hardcode the raw payload to test — it works.
The issue is almost always that you're validating the parsed body instead of the raw body. Stripe's signature is computed over the raw request body, not the JSON object your framework deserialized.
// Wrong — app.use(bodyParser.json()) already parsed this
app.post('/webhook', (req, res) => {
const sig = req.headers['stripe-signature'];
stripe.webhooks.constructEvent(req.body, sig, secret); // req.body is PARSED
});
// Right — use raw body
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
stripe.webhooks.constructEvent(req.body, sig, secret); // req.body is RAW
});
If you've ever fought a Stripe signature validation error for an hour, you know this pain.
Failure Mode 4: The Infrastructure Block
Your endpoint is behind an ngrok tunnel that expired. Your firewall is blocking incoming requests from Stripe's IP range. Your server restarted and the process isn't back up yet.
Stripe reports delivery failures. Your server never received the event. Nobody knows why.
The problem: you have no visibility into whether your endpoint is actually reachable from the public internet. You find out something is wrong when Stripe emails you about failed deliveries.
Failure Mode 5: The Retry Wait
Your handler has a bug. You fix it. You need to test it.
Stripe's retry schedule: 1 hour, 12 hours, 72 hours. That's potentially 78 hours of waiting to verify your fix works.
For payment webhooks, Stripe will eventually retry — but waiting that long in development is brutal. The alternative is triggering test events manually through the Stripe CLI, which works but doesn't capture the full production scenario.
The Better Debug Workflow
The common thread in all these failures: you can't see what's actually happening.
A webhook debugging tool gives you a dedicated endpoint that sits between the sender (Stripe, GitHub, etc.) and your server:
- Capture — the sender hits your debug endpoint instead of your server directly
- Inspect — you see the full raw payload, headers, and timing in a dashboard
- Replay — you resend the exact payload to your server instantly, without waiting for retries
This means you catch failures in seconds, not hours. You see what your server actually received — not what you hope it received.
Hooklog — Webhook Debugging Without the Wait
I built Hooklog to solve exactly this. It's free (10k events/month), requires no signup, and gives you:
- A unique webhook URL you can point any service at immediately
- Full payload inspection: headers, body, response, timing
- One-click replay to test your handler without waiting for retries
- Email alerts when your endpoint returns 4xx or 5xx
Get started: https://hooklog.c0c58bd.p.egbe.app
Free tier: 10,000 events/month, 3-day retention, up to 3 endpoints. No credit card required.
What's your worst webhook debugging story? Drop it in the comments — I read every one.
Top comments (0)