The webhook that failed every time
Payments work. Upgrades don't. 's dashboard shows every to your app failing with a 400.
This answers: Why does Stripe webhook verification fail? No coding needed. 3 quick decisions.
- Payments and webhooks
- Debugging without panic
Checking your access…
Why does Stripe webhook verification fail?
The signature covers the exact raw bytes sent. It fails when your code parses the body as JSON before verifying, or when it uses the wrong signing secret. In Next.js App Router, read the body with request.text() and pass it unchanged to constructEvent. Each endpoint, including the Stripe CLI, has its own whsec_ secret.
Breaks at Your server: Body parsed before the signature check
Why AI tools write this bug
AI tools reach for req.json() out of habit, and often copy the CLI's signing secret into production settings while testing locally.
How to fix it
- Read the raw body with request.text() before anything parses it.
- Verify with constructEvent, then use the returned event.
- Copy the whsec_ secret from the deployed endpoint in 's dashboard.
- Use separate secrets for CLI, test and live.
Check your own app
Paste this into Lovable, Cursor or Claude Code:
Without changing anything, show me how this project's Stripe webhook route reads the request body and which signing secret it uses. Is the raw body passed to constructEvent unchanged? Is anything parsing it first? Which environment variable holds the secret in development and in production?
Common questions
- Where do I see why a webhook failed?
- In Stripe's dashboard, under Webhooks, open the endpoint and a delivery attempt. It shows your server's status code and response.