GUIDE · STRIPE
Stripe webhook not working? Check these 6 things
Most Stripe webhook failures in production come from a signing secret that belongs to a different endpoint or mode, a request body that was parsed before verification, or a redirect or auth middleware in front of the route. Check the delivery log in the Stripe Dashboard first: the response code tells you which.
Read the response code
- 400 from your code: usually signature verification. See signature verification failed and payload must be the raw body.
- 301/302/307: a redirect (www, trailing slash, http to https). Point the endpoint at the final URL.
- 401/403: auth, CSRF or bot protection in front of the route.
- 404: wrong path, or the route was not deployed.
- 500 or timeout: your handler crashed or took too long. Check function logs.
The checklist
- Check the delivery log. In the Stripe Dashboard, open the webhook endpoint and read the response code for recent events.
- Match mode and secret. Test and live mode have separate endpoints and separate whsec_ signing secrets. Use the one for this endpoint and mode.
- Verify on the raw body. Pass the unparsed request body to constructEvent. Parsed JSON fails signature verification.
- Remove redirects and auth from the route. The endpoint must answer directly with 2xx: no redirect to www or https, no login or CSRF middleware.
- Subscribe to the right events. For Checkout, listen for checkout.session.completed and handle it idempotently.
- Respond fast. Return 2xx quickly and do slow work afterwards.
Next.js App Router example
export async function POST(req: Request) {
const body = await req.text(); // raw body, not req.json()
const sig = req.headers.get("stripe-signature")!;
const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
if (event.type === "checkout.session.completed") { /* mark paid, idempotently */ }
return new Response("ok", { status: 200 });
}
Test locally with the Stripe CLI: stripe listen --forward-to localhost:3000/api/webhooks prints its own signing secret for that session. Source: Stripe webhooks docs.
Get a $149 diagnosis See pricing Take Stripe live
- Fixed, or we keep working free until it is (within the written scope).
- A human reviews every fix before it ships.
- read-only access; fixes arrive as pull requests you merge
- Your $149 diagnosis is credited in full toward any paid job.
FAQ
Why does my webhook work locally but not in production?
The CLI uses its own signing secret. Production needs the secret of the deployed endpoint, in the matching mode (test or live).
Does Stripe follow redirects?
No. Treat any 3xx as a failure and point the endpoint at the exact final URL.
Should the browser mark an order as paid?
No. Payment status should come from the verified webhook or a server-side lookup, never from the success page.
Can you take Stripe live for me?
Yes. Stripe go-live is $400–800 fixed: checkout and webhooks working with live keys.