Tell Me When Down
How it worksWhat we checkPricing
BlogFree toolsCompareDocs
Log inGet started
All posts
Stripe webhooks you can trust

Stripe webhooks failing silently — and how to catch it

July 18, 2026·6 min read
A card payment terminal on a dark counter — a charge that went through while the app never heard about it.

A customer pays. Stripe takes the money. Your app never hears about it, so their account stays on the free plan — and the first you learn of it is a support email that starts with "I was charged but…".

Webhooks are how Stripe tells your app that money moved. When they fail, they fail in the worst possible way: the payment still succeeds, so nothing looks broken until a user tells you it is. Here's how they break, what Stripe does about it, and how to see it coming.

How webhooks fail without a sound

Stripe considers a webhook delivered only if your endpoint returns a 2xx status. Anything else — a 400, a 500, a timeout, a deploy that briefly 404s the route — counts as a failure and gets retried. The catch is that your users never see any of this. They paid; Stripe's side is fine.

The usual culprits:

  • The signature check rejects the event. Verifying the Stripe-Signature header needs the raw request body. Most frameworks parse the body to JSON before you see it, which changes the bytes and makes every verification fail with a 400.
  • The handler throws. A bug in the code that provisions access errors out and returns 500 — the payment went through, but the thing the payment was supposed to unlock never happened.
  • It's too slow. Do heavy work inline and the response times out; Stripe records a failure even though your code eventually finished.
  • The endpoint moved. A route rename or a bad deploy points Stripe at a URL that no longer answers.

What Stripe does when your endpoint fails

Stripe doesn't give up immediately — but it doesn't wait forever either. In live mode it retries a failing endpoint with exponential backoff for up to three days.

If it's still failing after that, Stripe automatically disables the endpoint and emails the account. Once disabled, new events aren't delivered there at all until you re-enable it by hand from the Dashboard.

2xx
the only response Stripe counts as delivered
3 days
it retries a failing endpoint, then stops
auto-disabled
endpoint switched off + emailed after that
raw body
required to verify the signature — parsers break it
Those three days of retries are your entire safety net. If a bad deploy breaks the endpoint on Friday and you don't look until Tuesday, Stripe may have already disabled it — and the events from that window are gone from the delivery queue.

Recovering the events you missed

Not all is lost after a failure. Stripe keeps the events, and you can replay them:

  1. In the Dashboard, open Developers → Webhooks, pick your endpoint, and look at its recent deliveries. Failed attempts are listed with the response code they got.
  2. Fix the handler and resend the failed events from that same view, or with the Stripe CLI's stripe events resend <id>.
  3. If the endpoint was auto-disabled, re-enable it first — otherwise the resent events have nowhere to go.

This only works if you catch it inside the window and your handler is idempotent, so replaying an event you partly processed doesn't double-charge or double-provision.

Build the handler so it can't fail quietly

Most silent webhook failures trace back to two things: the raw-body signature trap, and returning the wrong status code. This prompt builds a handler that verifies properly, stays idempotent, and only reports success when the work actually succeeded:

paste into Claude or ChatGPT
Write a production-ready Stripe webhook handler for my app.

Requirements:
- Read the RAW request body (not JSON-parsed) and verify the Stripe-Signature header against my webhook signing secret using stripe.webhooks.constructEvent. If verification fails, return 400 and do nothing else.
- Make it idempotent: Stripe can deliver the same event more than once, so record processed event IDs and skip an event I've already handled.
- Do the real work (e.g. provisioning access on checkout.session.completed) inside try/catch. On success return 2xx. On a real processing error return 500 so Stripe retries — but never return 2xx just to stop the retries when the work didn't actually succeed.
- Keep the handler fast: acknowledge quickly and, if the work is slow, hand it to a background job rather than blocking the response.
- Tell me the framework-specific gotcha for reading the raw body (I'll tell you my framework), and which events to listen for to activate a paid subscription.

Show me the full handler and the list of events to subscribe to.

Stripe's own webhooks documentation has the full event reference and signing details.

See the failure the day it starts

The Dashboard shows failed deliveries — but only if you go and look, and nobody checks the webhooks page on a quiet Saturday. That's the same blind spot a silently failing cron has: the information exists, but nothing pushes it to you.

Tell Me When Down watches the endpoint your billing depends on, so the moment it starts returning errors instead of 2xx, you get an email — inside the three-day window, while replay is still an option, and long before a customer writes in.

Don't let a broken webhook cost you a customer.

Join Tell Me When Down free and we'll watch your Stripe webhook endpoint around the clock. The moment it starts failing, you hear about it — in time to fix it and replay the events, not after Stripe has disabled it.

Watch my webhookfree · no card required
more on stripe webhooks you can trust
Stripe webhook signature verification failed: the raw-body fix"No signatures found matching the expected signature" almost always means a body parser changed the request bytes before verification ran. Here's what Stripe actually compares, and the raw-body fix for each framework.Stripe webhook duplicate events: stop the double chargeStripe's delivery is at-least-once, so the same event can arrive twice and charge a customer twice. Here's why it happens, and how to make your handler idempotent with the event id so running it twice is safe.Stripe subscription not activating after paymentPaid but still not upgraded? The subscription's status splits it in two: stuck in 'incomplete' means the payment never finalized; active-in-Stripe-but-not-your-app means a webhook never landed. Here's how to tell which.How to test Stripe webhooks locally (and what it won't prove)The Stripe CLI brings events to your machine — stripe listen forwards them, stripe trigger fires them. Here are the commands, the signing-secret gotcha, and why passing locally doesn't prove the live endpoint works.

spot something wrong or out of date? [email protected] — we'll fix it

Tell Me When Down

Uptime and security monitoring for people who'd rather ship than babysit servers. We watch so you can sleep.

product
How it worksWhat we checkPricingDocsBlogFAQ
free toolsWebsite security scanSupabase pause checkRender sleep checkMixed content checkerSecurity headers checkCookie security checkSSL expiry check
comparevs UptimeRobotvs Better Stackvs PingdomFor indie hackers
company
StatusAbout our botSupportPrivacyTerms
© 2026 TellMeWhenDown · tellmewhendown.com