Every Stripe integration has the same Achilles heel: webhook delivery failures are silent. Stripe retries for 72 hours across 15 attempts, then stops — and it never sends you a push notification, email, or alert when every attempt fails. Your dashboard only shows you if you go looking.
The result: a customer paid, Stripe sent the checkout.session.completed event, your endpoint returned 500, Stripe retried for three days, gave up, and now that customer's account is stuck in "pending" state forever. You find out when they email support.
Webhook endpoints fail for reasons that are hard to predict and don't follow your deployment schedule:
STRIPE_WEBHOOK_SECRET in production but not staging, breaking signature validation on one environment silentlyWhen your webhook endpoint returns a non-2xx status, Stripe queues retries on an exponential backoff schedule:
After 72 hours, the event is marked as failed. Stripe sends you a single "Your webhooks are failing" email during this window — to the account owner's email, not necessarily the developer, and often buried in Stripe summary digest emails. In practice, most teams don't see it until it's too late.
It's Friday evening. A deploy at 5pm breaks your webhook handler. Stripe starts retrying. By the time someone notices Monday morning — after a weekend of customer emails — 72+ hours have elapsed. Every event during that window is permanently failed. You have no way to replay them unless you built a replay mechanism yourself. You reconstruct orders manually from Stripe's event log.
Stripe gets the most attention, but every major provider has the same failure mode:
The pattern is the same everywhere: provider has exponential backoff, provider eventually gives up, your application state diverges from reality, you find out from users.
The naive approach is checking Stripe's webhook logs manually. That works if you're paranoid and check daily. Most teams don't.
A proper webhook failure monitor needs to:
Before you need an external monitor, your webhook handler should be robust. The two most common implementation mistakes:
// Bad: if the DB insert takes >20s, Stripe marks it failed
app.post('/webhooks/stripe', async (req, res) => {
const event = stripe.webhooks.constructEvent(...)
await db.insert('orders', buildOrder(event)) // this blocks
await sendConfirmationEmail(event) // and this
res.json({ received: true })
})
// Better: acknowledge immediately, process asynchronously
app.post('/webhooks/stripe', async (req, res) => {
const event = stripe.webhooks.constructEvent(...)
await queue.push({ event }) // enqueue for background processing
res.json({ received: true }) // respond immediately
})
Always verify stripe-signature against your webhook secret. Beyond security, this catches misconfigured secrets immediately instead of producing confusing downstream failures.
Stripe can and does deliver the same event more than once (especially after network issues). Use the event ID as an idempotency key:
const alreadyProcessed = await db.findOne('processed_events', { id: event.id })
if (alreadyProcessed) return res.json({ received: true })
// ... process event ...
await db.insert('processed_events', { id: event.id, processedAt: new Date() })
Even with a robust handler, failures happen. Infrastructure goes down. Dependencies have outages. Certificates expire.
The gap is passive monitoring — something that watches your webhook delivery health across all providers without you having to check dashboards daily.
I'm building exactly that: WebhookPulse monitors your webhook delivery across Stripe, GitHub, Twilio, Shopify, SendGrid, and more. When a delivery starts failing, you get alerted immediately — with the full retry history and one-click replay for recoverable events. $29/month flat.
WebhookPulse monitors your Stripe, GitHub, Twilio, and Shopify webhook delivery in real time — and alerts you at the first failure, not 72 hours later.
Join the early access waitlist →Related: CrawlGuard — monitor AI crawler traffic spiking your infrastructure bill.