Stripe webhook signature verification failed: fix
Stripe raises this when one of the three inputs to constructEvent() is wrong: the raw request body, the Stripe-Signature header, or the endpoint secret. Pass the unmodified raw body (not parsed JSON) and the whsec_ secret for that exact endpoint.
Cause: Wrong endpoint secret (often CLI vs Dashboard secret mixed up) or a framework parsed/re-serialized the body before verification.
How to fix it
- Confirm the
whsec_secret matches the endpoint receiving events (Dashboard endpoint secret vsstripe listensecret are different). - Pass the raw body:
express.raw({type: 'application/json'})in Express, orawait req.text()in Next.js route handlers. - Read the
Stripe-Signatureheader; it should look liket=...,v1=.... - Use separate secrets for test and live mode endpoints, and redeploy after changing env vars.
Example
Illustrative example, not from a customer project.
- app.post('/webhook', express.json(), (req, res) => {
+ app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
FAQ
It works with the Stripe CLI but fails in production. Why?
The CLI prints its own whsec_ secret. Production needs the secret of the Dashboard endpoint.
Can I just skip verification?
You shouldn't. Verification is how you know the event came from Stripe.
Get a $149 diagnosis See pricing Stripe still not live? We take it live at a fixed price.
- 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.