Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch from misaligned endpoint secret or raw body handling—verify secret matches live environment and use raw request body, not parsed JSON.
Stripe: webhook signature verification failing [2026 fix]
TL;DR
Cause: Your endpoint secret is either wrong, from the wrong environment, or your code is verifying against a parsed JSON body instead of the raw request stream.Fix: Use the correct live/test secret for your environment and always verify against req.rawBody (not req.body), then parse after verification.
---
Real Console Error Messages
``` No signature header 'stripe-signature' found ```
``` Webhook signature verification failed. Webhook ID: we_xxxxxxxxxx ```
``` Signature verification failed. Possibly due to a timestamp outside the time window. ```
``` Invalid signature for payload. ```
``` Error: Unable to extract timestamp and signatures from header ```
---
Broken Code vs. Fixed Code
Problem 1: Using Parsed Body Instead of Raw Body
BROKEN ❌
```javascript
app.post('/webhook', express.json(), (req, res) => {
const sig = req.headers['stripe-signature'];
try {
// WRONG: req.body is already parsed JSON
const event = stripe.webhooks.constructEvent(
req.body, // ← This is the problem
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
res.json({received: true});
} catch (err) {
res.status(400).send(Webhook Error: ${err.message});
}
});
```
FIXED ✅
```javascript
app.post(
'/webhook',
express.raw({type: 'application/json'}), // ← Raw body middleware
(req, res) => {
const sig = req.headers['stripe-signature'];
try {
// CORRECT: req.body is Buffer, not parsed
const event = stripe.webhooks.constructEvent(
req.body, // ← Now this is the raw request stream
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
res.json({received: true});
} catch (err) {
res.status(400).send(Webhook Error: ${err.message});
}
}
);
```
Problem 2: Wrong Endpoint Secret (Test vs. Live)
BROKEN ❌ ```javascript const endpointSecret = 'whsec_test_1234567890'; // Always test?
const event = stripe.webhooks.constructEvent( body, sig, endpointSecret // ← Mismatched environment ); ```
FIXED ✅ ```javascript // Use correct secret for current environment const endpointSecret = process.env.NODE_ENV === 'production' ? process.env.STRIPE_WEBHOOK_SECRET_LIVE : process.env.STRIPE_WEBHOOK_SECRET_TEST;
const event = stripe.webhooks.constructEvent( body, sig, endpointSecret // ← Now matches webhook source ); ```
Problem 3: Parsing Body Before Verification
BROKEN ❌ ```javascript app.use(express.json());
app.post('/webhook', (req, res) => { const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( JSON.stringify(req.body), // ← Stringify of parsed = wrong hash sig, process.env.STRIPE_WEBHOOK_SECRET ); }); ```
FIXED ✅ ```javascript app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( req.body, // ← Buffer/stream, not stringified sig, process.env.STRIPE_WEBHOOK_SECRET ); // Parse AFTER verification const data = JSON.parse(req.body); }); ```
---
Step-by-Step Verification Checklist
1. Confirm your endpoint secret (Stripe Dashboard → Developers → Webhooks → click endpoint → "Signing secret") 2. Check environment variables loaded correctly: ```javascript console.log('Secret ends with:', process.env.STRIPE_WEBHOOK_SECRET?.slice(-4)); ``` 3. Inspect headers at runtime: ```javascript console.log('Stripe signature header:', req.headers['stripe-signature']); ``` 4. Verify middleware order — raw body parser must come *before* JSON parser 5. Test with Stripe CLI locally: ```bash stripe listen --forward-to localhost:3000/webhook stripe trigger payment_intent.succeeded ```
---
Still broken? Check these too
1. [Webhook endpoint DNS/network issues](/?guide=webhook-dns) — Stripe can't reach your URL if it's behind a firewall or uses self-signed SSL. Test with curl https://your-endpoint.com/webhook.
2. [Timestamp validation outside 5-minute window](/?guide=stripe-timestamp) — System clock drift causes verification to fail. Run ntpdate -s time.nist.gov on your server.
3. [Multiple webhook endpoints with same secret](/?guide=stripe-duplicate-webhooks) — If you have test and live endpoints sharing secrets, Stripe rejects requests. Create separate signing secrets per endpoint in the Dashboard.
---
Official Documentation
📖 [Stripe Webhooks Security - Official Docs](https://stripe.com/docs/webhooks/signatures)
📖 [Stripe Node.js SDK - constructEvent](https://stripe.com/docs/api/nodejs#construct_event)
---
Found a different variation? Drop it in the comments — including your framework (Next.js, FastAPI, etc.) and error message. We update this guide based on community reports.