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

The checklist

  1. Check the delivery log. In the Stripe Dashboard, open the webhook endpoint and read the response code for recent events.
  2. Match mode and secret. Test and live mode have separate endpoints and separate whsec_ signing secrets. Use the one for this endpoint and mode.
  3. Verify on the raw body. Pass the unparsed request body to constructEvent. Parsed JSON fails signature verification.
  4. 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.
  5. Subscribe to the right events. For Checkout, listen for checkout.session.completed and handle it idempotently.
  6. 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

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.

Related

Check it yourself first, free

$149Get a $149 diagnosis

FREE · LAUNCH-READY CHECKLIST

Before you go: is your app ready for real users?

Get our 40-point launch-ready checklist for AI-built apps: data access, secrets, auth, payments, deploy, email and backups. It opens straight away.

No payment. See our privacy policy.