Symptoms / Fix guide

Why is my Stripe webhook not firing?

Matt Turley, Continuum. Updated November 5, 2026.

For founders whose payment succeeds in Stripe but nothing happens in the app afterwards: no access granted, no order created, no email sent.

The short answer

Why is my Stripe webhook not firing?

Stripe is almost always sending the event; your server is refusing it or never receives it. Open the delivery log for your endpoint in the Stripe Dashboard. A redirect, a 401 from deployment protection or auth middleware, a 400 from a failed signature check, or a 500 from your handler each point to a different fix.

What is really going on

"Not firing" covers three different failures, and the fix depends on which one you have. Either Stripe never tried to send the event, or it tried and your server never received it, or your server received it and rejected it. The delivery log for your webhook endpoint in the Stripe Dashboard tells you which in about a minute, before you read any code.

Webhooks matter more than they look. A checkout success page is only a browser redirect: if the customer closes the tab, or the redirect fails, nothing runs. The webhook is the only signal that is sent to your server by Stripe itself, which is why access, orders and receipts should be driven by it.

AI-generated webhook handlers tend to work against the Stripe CLI on a laptop and then fail in production for reasons the CLI hides: a different signing secret, a route that auth middleware now protects, or a body that was parsed before it was verified.

The usual causes, most common first

  1. The endpoint is registered in the wrong mode

    How to tell Test mode and live mode have separate endpoints, separate events and separate signing secrets. If you are taking real payments but only registered an endpoint in test mode, the live endpoint list is empty and live events go nowhere.

    The fix Register the endpoint in the mode you are charging in, and set that mode's signing secret (it starts with whsec_) in your production environment variables.

  2. The endpoint is not subscribed to the event you handle

    How to tell The payment appears in the Dashboard event list, but the endpoint's delivery log has no attempt for it. Your handler may listen for checkout.session.completed while the endpoint was created with a different event list.

    The fix Add the exact event types your handler switches on to the endpoint. Events you do not handle should be unsubscribed, not ignored, to keep the log readable.

  3. The URL redirects before it reaches your handler

    How to tell The delivery log shows a 301, 307 or 308. Typical triggers: the apex domain redirecting to www, a trailing-slash setting, or http redirecting to https. A redirect is not a 2xx, so Stripe counts the delivery as failed.

    The fix Register the final URL, exactly as it answers with no redirect. Test it with curl -i -X POST https://your-domain/api/webhooks/stripe and confirm you do not get a 3xx.

  4. Something in front of the route blocks it

    How to tell The log shows 401 or 403, and the response body is an HTML page. Common sources are Vercel Deployment Protection on a preview URL, and auth middleware (Clerk, NextAuth, Supabase) whose matcher covers every route including the webhook.

    The fix Point webhooks at the production URL, not a preview URL. Exclude the webhook path from the auth middleware matcher. The webhook authenticates by signature, not by session.

  5. The signature check fails

    How to tell The log shows 400 and your function log says "No signatures found matching the expected signature". Three causes account for nearly all of it: you used the whsec_ secret printed by stripe listen against the Dashboard endpoint (or the reverse), you used the wrong mode's secret, or the body was parsed as JSON before it was verified.

    The fix Verify against the raw request body. In a Next.js route handler that means await request.text() and passing the string to stripe.webhooks.constructEvent, not request.json(). Use the secret that belongs to the endpoint you are testing.

  6. The handler throws, or runs too long

    How to tell The log shows 500 or a timeout. A handler that calls your database, a CRM and an email service in sequence on a serverless function can exceed its time limit, and any unhandled error becomes a 500.

    The fix Verify the signature, record the event id, return 2xx quickly, and do the slow work after. Stripe retries failed deliveries on a schedule, and once the handler is fixed you can resend the failed events from the Dashboard.

How we diagnose it

  1. Classify the failure from the delivery log. Open the endpoint in the Stripe Dashboard and read the status of the latest attempts: none at all, a redirect, 401 or 403, 400, or 5xx. This single step eliminates most of the list above.
  2. Confirm mode, account and endpoint list. Check that you are looking at the same mode you charged in, that the endpoint exists there, and that its subscribed events include the ones your code handles.
  3. Hit the URL yourself without Stripe. POST to the endpoint with curl and read the response headers. A redirect, an HTML login page or a 404 shows up immediately, with no webhook tooling involved.
  4. Read the route and the middleware together. We read the handler, the middleware matcher and the framework config as one path: does the request reach the handler, is the body still raw when it gets there, and is the right secret loaded in the production scope?
  5. Replay one real event. Resend a single failed event from the Dashboard, or with stripe events resend, and watch the production function log while it arrives. A fix is confirmed only when a real event from the failing mode returns 2xx and the side effect happens once.

What fixing it changes

  • The endpoint, its subscribed events and its signing secret match, in the mode that takes payments.
  • The route sits outside the auth middleware and answers without a redirect.
  • The signature is verified against the raw body, and a bad signature returns 400 without running any logic.
  • The handler records each event id so a retry or duplicate delivery cannot grant access or create an order twice.
  • Failed events from the outage are replayed so no paid customer is left without access.

When you do not need us

If the delivery log shows 200s and the app is still wrong, delivery is fine and the bug is in what your handler does with the event. That is ordinary debugging, and an AI coding assistant with the log and the handler in front of it can usually find it. You need a human when the cause is spread across middleware, hosting and handler, or when you have already paid customers waiting.

Your next step

The webhook is one part of the payment path. A Ship Check reads the whole codebase, so it covers the handler, the middleware in front of it and what happens after the event, and ranks what will break first.

Ship Check, $299: A senior engineer reads your codebase and sends a written verdict (fine, fixable or rewrite), what will break first, a security and data read, and a costed plan, within 48 hours of repo access or it is free. It has a 7-day money-back and is credited against a Ship Fix. Or: Book a call.

Get a Ship Check, $299 →See all services and prices →
FAQ

Straight answers

Does Stripe retry a webhook that failed?
Yes. Stripe retries failed deliveries on a backoff schedule, and the Dashboard lets you resend an event by hand. That is why recording each event id matters: the same event can legitimately arrive more than once, and your handler must not repeat its side effects.
Why does it work with the Stripe CLI but not in production?
The CLI forwards events to your laptop with its own signing secret and bypasses your hosting, DNS and middleware. Production adds all three, plus a different secret. The usual culprits are the secret, the middleware matcher and a redirect on the domain.
Can I just use the checkout success page instead?
Not on its own. The success page depends on the customer's browser reaching it. If they close the tab or lose connection after paying, your app never learns about the payment. Use the webhook as the source of truth and treat the success page as a convenience.
Is it safe to skip signature verification while I debug?
Do it only on a local machine, never on a deployed URL. An endpoint that accepts unsigned events lets anyone post a fake "payment succeeded" to your server and trigger fulfilment for free.
Keep reading
A customer paid, but the order is not in my CRM. Why?By symptom. Payment and CRM record are two separate writes joined by your code. Where the chain usually breaks, how to trace one order end to end, and how to fix it.Why is my app a white screen after a Vercel deploy?By symptom. A white screen after deploy means the page JavaScript failed or its files did not load. How to read the console, the usual causes, and the fix for each.What breaks in an AI-built e-commerce store?By vertical. Online stores built with AI tools: prices set in the browser, overselling, webhooks, tax, refunds and card data scope. What breaks and how to test it.What breaks in an AI-built marketplace app?By vertical. Two-sided marketplaces built with AI tools: seller onboarding, payouts, fees, refunds, disputes and who sees whose data. What breaks and how to test it.What should I check before launching my AI-built app?By stage. A launch-week checklist for apps built with AI tools: who can see whose data, secrets, the real payment path, email, backups, monitoring and a rollback plan.My app broke right after launch. What do I do first?By stage. The first hours after a launch goes wrong: stop the damage, find the failure, protect users and data, then make it stable. What usually breaks, in order.
Sources
  1. 1. Stripe: Receive Stripe events in your webhook endpoint, Stripe: Endpoint requirements, retries, resending events and handling duplicate deliveries.
  2. 2. Stripe: Resolve webhook signature verification errors, Stripe: Why verification needs the raw body and the right endpoint secret.
  3. 3. Vercel: Deployment Protection, Vercel: Why a preview URL answers an external caller with an authentication page.
Why is my Stripe webhook not firing? | Continuum