Stripe Webhook Not Working — How to Fix Missing Payment Notifications
You are looking at a row of green checkmarks in Stripe that say every webhook delivered, and a server log that shows none of them ever arrived. Both screens are telling the truth. That is the trap you are in. Stripe genuinely sent the events. Your handler genuinely never ran. And somewhere between those two facts your customers are getting charged while their accounts stay locked, silently, with nothing on fire and no error to grep for.
“You built the webhook endpoint. Stripe says it delivered. Your logs show nothing. The order never fulfilled.”
— DEV Community / deadletter, 2024
You found out the worst way, the way nearly everyone does: a user emailed asking why their account is locked after they paid. So before you touch a single line of code, here is what to check.
The fastest fix: verify the endpoint URL first
Check that Stripe is pointing at the right address. This one fix clears roughly 40% of silent webhook failures with no code change at all.
Open Stripe, Developers, Webhooks, your endpoint, and compare the configured URL against what your server is actually listening on after your last deploy. Bolt.new, Vercel, and Netlify can all quietly reshape your URL structure on a redeploy, and Stripe has no way to know it moved. If the two do not match exactly, every event is being delivered to an address where nothing lives. Click “Resend” on the most recent event and watch your server logs in real time. Nothing arrives? Mismatch confirmed. Update the endpoint in Stripe, resend, and you are back.
Five checks when the URL is correct
“Your endpoint returns 200 OK to Stripe. But your handler never fires.”
— DEV Community / James Brown, 2024
Delivered, but your handler never fired
This is the signature failure of the whole category. Stripe marks the event delivered, your order or email or subscription logic never processes, and no error appears anywhere. Add a single log line at the very top of your webhook handler, before any parsing, before the signature check, before anything. Then resend from Stripe’s dashboard and watch for it. If that line never prints, something in front of your code, a catch-all route or a middleware layer or a reverse proxy, returned 200 to Stripe before the request ever reached your handler. Stripe saw the 200, called it delivered, and moved on. Your code was never in the room. Check your routing configuration first, because no amount of staring at business logic will help when the request never got that far.
The signature check is rejecting Stripe silently
Different shape: requests do arrive, then stop dead in your logs, and Stripe marks them failed after getting back an error response. That is signature verification refusing the request before your logic ever runs. The culprit is almost always the signing secret. STRIPE_WEBHOOK_SECRET in your production environment has to match Stripe’s dashboard value exactly, down to trailing whitespace, and a single stray character invalidates every event. Pull it from Stripe under Developers, Webhooks, your endpoint, Signing secret, click reveal, and compare. The alert you want here is “Stripe webhook rejected, signing secret mismatch, check STRIPE_WEBHOOK_SECRET in production env vars.” The one you get is an unreadable verification error that buries the one fact you need.
Test mode is armed, live mode is empty
Webhooks fired perfectly all through development, then live production payments produce nothing at all. That clean split is the tell. Stripe’s test-mode and live-mode endpoints are entirely separate worlds, and a test-mode endpoint never sees a real transaction. Open Developers, Webhooks, and check which mode the endpoint lives in. Your production app needs one configured in live mode. This catches a lot of fast-shipped apps, because everything gets tested thoroughly in test mode and the live-mode endpoint is a separate setup step that is genuinely easy to forget on launch day.
The deploy moved the URL out from under Stripe
Webhooks worked right up until the last deploy and stopped the moment after. Pull your hosting provider’s deployment logs and look for any shift in the URL or path. AI-assisted deploys are especially good at moving a path without making it obvious you moved it, and some providers hand preview deployments their own URLs that only work in test mode. It’s the same preview-versus-production gap that breaks vibe-coded apps after a deploy, walked through in bolt.new preview vs production: the environment that worked is not the environment that’s live. Confirm Stripe is pointing at your stable production URL, not a preview that happened to work the day you set it up, then update the endpoint if it drifted.
Your server is returning 500, and Stripe is about to give up
The cruelest one, because it is on a timer.
“For recurring products, this is where 20-40% of involuntary churn comes from.”
— heze (commenter), IndieHackers, 2025
Here Stripe shows the events as “failed” with several retry attempts, and subscriptions start canceling on their own. Click into a failed event in the webhook log to see every retry and the exact response code behind it. A 500 means your handler threw an unhandled exception. Stripe will retry that event over about three days, roughly five attempts with growing gaps between them, and then it stops trying forever. Anything your subscription logic should have done in that three-day window has to be replayed by hand from the dashboard afterward, and every one you miss is a paying customer who quietly churns out without ever telling you why.
The silent revenue gap you cannot watch from inside
Run the numbers on the situation you are in. Some number of events, delivered to a void, over some number of days, and the first sensor that fired was a customer’s frustration. Stripe’s “delivered” and your handler actually running are two different facts, and almost no Stripe setup has any way to see the space between them from the outside. From inside your code, a webhook that never arrived looks exactly like a quiet day. There is nothing to log, because nothing happened, and “nothing happened” is precisely what a missed payment looks like too.
From inside, a missed payment looks exactly like a quiet day, which is the same trap behind every silent automation failure: the absence of an error is not the presence of success. It’s also why a crashed AI-built app can keep returning healthy responses while doing none of the work it’s supposed to.
That blind spot is the thing to close. NoCrash watches whether your live app is actually processing what it should, payments flowing, accounts unlocking, from the outside where your customers stand, and tells you in plain language the moment that stops. Connect your app free at nocrash.io and the next void swallows a message to you instead of three days of revenue.
So add that one log line at the top of your handler today, and set up the outside check while you are in there. Future-you, reading “payments stopped processing 11 minutes ago” instead of a refund request from a locked-out customer, will be glad you did.