You're losing Stripe webhooks and you don't even know it.
Stripe's webhook system is solid, but your receiving end isn't. Here are the 5 most common failure scenarios and exactly how to fix them.
1. Your Server Is Down During Deploys
The most common cause. You deploy, your server restarts, and during that 10-30 second window, Stripe sends a webhook. Your server returns a 502. Stripe will retry, but if your deploys are frequent or your downtime is longer, you're in trouble.
Stripe's retry behavior: 16 attempts over approximately 3 days, with exponential backoff.
After that? The event is gone. You'd need to manually poll the Events API to recover it.
Fix: Use a webhook proxy that accepts webhooks even when your server is down, then delivers them when it's back up.
2. Your Handler Times Out
Stripe expects a response within 20 seconds. If your webhook handler does heavy processing (database writes, external API calls, email sends), you might exceed this.
Stripe sees a timeout as a failure and retries. Now you're processing the same event multiple times.
Fix:
// Bad: Process everything synchronously
app.post('/webhook', async (req, res) => {
await processPayment(req.body); // 5s
await updateDatabase(req.body); // 3s
await sendEmail(req.body); // 8s
await notifySlack(req.body); // 4s
res.sendStatus(200); // Total: 20s - too slow!
});
// Good: Acknowledge immediately, process async
app.post('/webhook', async (req, res) => {
res.sendStatus(200); // Respond in <100ms
// Process in background
queue.add('process-webhook', req.body);
});
3. Wrong Response Codes
Your server returns 301 Moved Permanently because you have HTTP→HTTPS redirects. Or it returns 401 Unauthorized because your auth middleware intercepts the webhook endpoint.
Stripe only counts 2xx as success. Everything else triggers a retry.
Fix: Ensure your webhook endpoint:
- Is served over HTTPS directly (no redirects)
- Is excluded from auth middleware
- Returns
200or204explicitly
4. Signature Verification Fails
You're comparing the wrong payload. Common mistakes:
// Wrong: Using parsed JSON body
const event = stripe.webhooks.constructEvent(
JSON.stringify(req.body), // This re-serializes differently!
sig,
secret
);
// Right: Using the raw body
const event = stripe.webhooks.constructEvent(
req.rawBody, // Exact bytes Stripe sent
sig,
secret
);
Express, Next.js, and most frameworks parse the body before your handler sees it. You need the raw body for signature verification.
5. Firewall or WAF Blocks
Your CDN or WAF (Cloudflare, AWS WAF) blocks Stripe's webhook requests because they look suspicious — high volume POST requests from unknown IPs.
Fix: Whitelist Stripe's webhook IPs or, better yet, use signature verification instead of IP whitelisting (IPs can change).
What Happens After Stripe Stops Retrying?
After 16 failed attempts over 3 days, Stripe marks the event as failed and stops trying. Your options:
- Manual recovery: Use the Stripe Dashboard → Events to find and manually resend
-
API polling: Periodically call
stripe.events.list()to find events you missed - Webhook proxy: Use a service that sits between Stripe and your server, guaranteeing delivery
Option 3 is the only one that doesn't require ongoing manual work.
The Reliability Layer Approach
Instead of pointing Stripe directly at your server, point it at a webhook proxy:
Stripe → EventDock (always up) → Your Server (might be down)
EventDock accepts the webhook immediately (sub-100ms, runs on Cloudflare's edge), stores it, verifies the Stripe signature, and then delivers it to your server. If your server is down, EventDock retries with exponential backoff for hours.
Setup takes 2 minutes:
- Create an account at eventdock.app
- Create an endpoint pointing to your server
- Copy the EventDock URL into Stripe's webhook settings
You get:
- Automatic retries (7 attempts over 2+ hours)
- Dead letter queue for permanently failed webhooks
- One-click replay from the dashboard
- Full request/response logging
- Real-time failure alerts
Prevention Checklist
Before your next deploy, verify:
- [ ] Webhook endpoint returns
200in <5 seconds - [ ] Raw body is preserved for signature verification
- [ ] Endpoint is excluded from auth middleware
- [ ] No HTTP→HTTPS redirects on the webhook path
- [ ] Firewall allows Stripe's IPs
- [ ] Idempotency keys prevent duplicate processing
- [ ] You have monitoring/alerts on webhook failures
Originally published at eventdock.app/blog
What's the worst webhook failure you've experienced? Drop a comment below.
Top comments (0)