DEV Community

EventDock
EventDock

Posted on • Originally published at eventdock.app

Why Your Stripe Webhooks Are Failing (And How to Fix It)

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);
});
Enter fullscreen mode Exit fullscreen mode

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 200 or 204 explicitly

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
);
Enter fullscreen mode Exit fullscreen mode

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:

  1. Manual recovery: Use the Stripe Dashboard → Events to find and manually resend
  2. API polling: Periodically call stripe.events.list() to find events you missed
  3. 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)
Enter fullscreen mode Exit fullscreen mode

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:

  1. Create an account at eventdock.app
  2. Create an endpoint pointing to your server
  3. 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 200 in <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)