Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch caused by endpoint secret mismatch or missing raw body. Verify STRIPE_WEBHOOK_SECRET matches dashboard and use raw request body.
Stripe: Webhook Signature Verification Failing [2026 Fix]
TL;DR
Cause: Your endpoint is using the wrong webhook secret or passing JSON-parsed body instead of raw request body to verification. Fix: Match yourSTRIPE_WEBHOOK_SECRET to Stripe Dashboard > Webhooks > Signing Secret, and verify you're passing req.rawBody (not req.body) to stripe.webhooks.constructEvent().---
Exact Console Error Messages
``` Error: No signatures found matching the expected signature for payload. at Webhook.constructEvent (/node_modules/stripe/lib/resources/Webhooks.js:45:12) ```
``` Stripe signature verification failed: error decoding body at verifyWebhookSignature (webhook.js:23:15) ```
``` No match found for signature header 't=1704067200,v1=abcd1234...' error: "Unable to extract timestamp and signatures from header" ```
``` Error: Signature timestamp outside the tolerance window tolerance: 300 seconds timestamp: 1704067200 ```
``` TypeError: Cannot read property 'split' of undefined at constructEvent (stripe/webhooks.js:51:8) ```
---
Code Comparison: Broken vs. Fixed
❌ BROKEN (Express.js example)
```javascript // webhook.js - WRONG const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const express = require('express'); const app = express();app.use(express.json()); // This parses body - PROBLEM!
app.post('/webhook', (req, res) => {
const sig = req.headers['stripe-signature'];
try {
// req.body is already parsed JSON, not raw bytes
const event = stripe.webhooks.constructEvent(
req.body, // WRONG - this is parsed object
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
res.json({ received: true });
} catch (err) {
console.error('Webhook error:', err.message);
res.status(400).send(Webhook Error: ${err.message});
}
});
```
✅ FIXED (Express.js example)
```javascript // webhook.js - CORRECT const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const express = require('express'); const app = express();// CRITICAL: Parse raw body ONLY for webhook endpoint
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
try {
// req.body is now Buffer (raw bytes) - CORRECT
const event = stripe.webhooks.constructEvent(
req.body, // Now this is raw bytes, not parsed
sig,
process.env.STRIPE_WEBHOOK_SECRET // Must match Stripe Dashboard
);
// Handle event type
switch (event.type) {
case 'payment_intent.succeeded':
console.log('Payment successful:', event.data.object.id);
break;
case 'charge.failed':
console.log('Payment failed:', event.data.object.id);
break;
}
res.json({ received: true });
} catch (err) {
console.error('Webhook error:', err.message);
res.status(400).send(Webhook Error: ${err.message});
}
});
// Other routes can use normal JSON parser app.use(express.json()); app.post('/api/other', (req, res) => { // normal body parsing here }); ```
---
Verification Checklist
1. Webhook Secret Mismatch (Most Common)
whsec_).env: STRIPE_WEBHOOK_SECRET=whsec_xxxxprocess.env.STRIPE_WEBHOOK_SECRET2. Raw Body Requirement
express.raw() or equivalent for that route onlypackage.json version3. Timestamp Tolerance
---
Still broken? Check these too
1. Environment Variable Not Loading — Verify console.log(process.env.STRIPE_WEBHOOK_SECRET) outputs the correct secret (not undefined). Restart your server after changing .env.
2. Multiple Webhooks with Different Secrets — If you registered multiple endpoints in Stripe Dashboard, each has a unique signing secret. Confirm you're using the secret for *this specific* endpoint URL.
3. Test vs. Live Secret Mismatch — Are you testing in live mode but using a test-mode signing secret? Dashboard has separate signing secrets for test and live modes—toggle the mode switch in top-left corner.
---
Related Guides
---
Official Documentation
📖 [Stripe Webhook Signature Verification](https://stripe.com/docs/webhooks/signatures) 📖 [Stripe.js Webhooks API Reference](https://github.com/stripe/stripe-node#webhook-signing)
---
Found a different variation? Drop it in the comments.