Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch due to raw body parsing or endpoint secret misconfiguration. Use raw body buffer and verify correct secret from Dashboard.
Stripe: Webhook Signature Verification Failing [2026 Fix]
TL;DR
Cause: You're parsing the request body as JSON before verification, or using the wrong endpoint secret. Fix: Pass the raw request body (as Buffer) tostripe.webhooks.constructEvent() and confirm your endpoint secret matches Dashboard → Developers → Webhooks.---
Real Console Error Messages
``` Error: No signatures found matching the expected signature for payload. ```
``` WebhookSignatureVerificationError: No matching signature found for payload. Details: expected_sig=whsec_..., received_sig=undefined ```
``` StripeSignatureVerificationError: Webhook signature verification failed. Timestamp outside tolerance window. Tolerance: 300, Current: 450 seconds ago ```
``` Error: Webhook endpoint secret is invalid or not found in environment ```
``` TypeError: Cannot read property 'id' of undefined - webhook event failed to construct ```
---
Broken Code vs. Exact Fix
Problem #1: JSON Parsing Before Verification
BROKEN: ```javascript app.post('/webhook', (req, res) => { // ❌ Body already parsed to JSON - signature check will fail const body = JSON.parse(req.body); const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( body, // ❌ WRONG: Already stringified/parsed sig, process.env.STRIPE_WEBHOOK_SECRET ); }); ```
FIXED:
```javascript
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
// ✅ req.body is Buffer - kept raw for signature verification
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body, // ✅ CORRECT: Raw Buffer
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
// Now safe to use event
console.log('Event type:', event.type);
res.json({ received: true });
} catch (error) {
console.error('Webhook signature verification failed:', error.message);
res.status(400).send(Webhook Error: ${error.message});
}
});
```
Problem #2: Wrong Endpoint Secret
BROKEN: ```javascript const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_SECRET_KEY // ❌ WRONG: This is your API key, not webhook secret ); ```
FIXED: ```javascript const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET // ✅ CORRECT: whsec_live_... or whsec_test_... ); ```
Problem #3: Using Body Parser Middleware Globally
BROKEN: ```javascript app.use(express.json()); // ❌ Parses ALL bodies before route handlers
app.post('/webhook', (req, res) => { const event = stripe.webhooks.constructEvent( req.body, // ❌ Already parsed - signature fails sig, process.env.STRIPE_WEBHOOK_SECRET ); }); ```
FIXED: ```javascript // Apply JSON parsing to all routes EXCEPT webhook app.use((req, res, next) => { if (req.path === '/webhook') { return next(); // Skip middleware for webhook } express.json()(req, res, next); });
// OR: Use raw body ONLY for this endpoint app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const event = stripe.webhooks.constructEvent( req.body, // ✅ Raw Buffer preserved sig, process.env.STRIPE_WEBHOOK_SECRET ); });
// Other routes get JSON parsing normally app.post('/api/charges', express.json(), (req, res) => { // req.body is parsed JSON here - safe }); ```
---
Verification Checklist
1. Get the correct webhook secret:
- Go to [Stripe Dashboard](https://dashboard.stripe.com/developers/webhooks)
- Find your endpoint URL
- Copy the "Signing secret" (starts with whsec_)
- Add to .env: STRIPE_WEBHOOK_SECRET=whsec_live_xxx
2. Confirm raw body is used: ```bash # In your logs, req.body should be a Buffer: console.log(typeof req.body, Buffer.isBuffer(req.body)); // Output: object true ✅ ```
3. Check environment variables are loaded: ```javascript console.log('Webhook secret:', process.env.STRIPE_WEBHOOK_SECRET?.substring(0, 10) + '...'); // Should print: whsec_live_... or whsec_test_... ```
---
Still Broken? Check These Too
1. [Stripe API Key Configuration Errors](/guide=stripe-api-keys) — Using test key in production or vice versa causes signature mismatches.
2. Request Body Size Limits — Nginx/reverse proxy truncating webhook payload. Set client_max_body_size 10m; in nginx config.
3. [Clock Skew / Timestamp Validation](/guide=webhook-timestamp-errors) — Server time drifted >5 minutes from actual time. Run ntpdate -s time.nist.gov on Linux or sync system clock.
4. Multiple Webhook Endpoints — Testing against wrong endpoint URL. Verify in Stripe Dashboard that your URL matches exactly (domain, path, protocol).
5. Old Stripe SDK Version — Versions <11.0 have different signature validation. Update: npm install stripe@latest.
---
Official Documentation
---
Found a different variation? Drop it in the comments.