Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch occurs when endpoint secret is wrong or request body is altered. Verify exact secret from Dashboard and ensure raw request body is passed to verification.
Stripe: webhook signature verification failing [2026 fix]
TL;DR
Cause: Your webhook endpoint secret is mismatched, or the request body was parsed/modified before signature verification.
Fix: Use the exact secret from Stripe Dashboard's Webhook Settings, verify raw request body is passed to constructEvent(), and ensure middleware order doesn't modify the body.
---
Exact Console Error Messages
``` Error: No signatures found matching the expected signature for payload. at constructEvent (/node_modules/stripe/lib/resources/WebhookEndpoints.js:92:15) at Object.constructEvent [as default] (/app/webhooks/stripe.js:15:8) ```
``` error: Webhook signature verification failed: invalid signature timestamp=1672531200, signatures=["v1=abcd1234..."], secret=whsec_live_xxxx ```
``` Stripe.error.StripeSignatureVerificationError: No signatures found matching the expected signature for payload. at verifySignatureHeaders (/node_modules/stripe/lib/WebhookEndpoint.js:118:22) ```
``` Error: Webhook endpoint received request with mismatched signature Expected: v1=xyz789 Received: v1=abc123 Timestamp: 1672531234567 ```
``` TypeError: Cannot read property 'id' of undefined at webhook handler (Signature verification passed but event object is null) ```
---
Broken Code vs. Fixed Code
❌ BROKEN: Using parsed JSON body
```javascript // app.js const bodyParser = require('body-parser'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.use(bodyParser.json());
app.post('/webhook', (req, res) => {
// WRONG: body is already parsed
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body, // ← PROBLEM: this is Object, not raw string
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
res.json({received: true});
} catch (err) {
res.status(400).send(Webhook Error: ${err.message});
}
});
```
✅ FIXED: Using raw request body before parsing
```javascript // app.js const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
// Apply JSON parser AFTER webhook route
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body, // ← CORRECT: raw Buffer from express.raw()
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
// Now safe to handle event
if (event.type === 'payment_intent.succeeded') {
console.log('Payment successful:', event.data.object.id);
}
res.json({received: true});
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
res.status(400).send(Webhook Error: ${err.message});
}
});
// NOW apply body parser for other routes app.use(express.json()); ```
Environment Variable Verification
```bash
❌ WRONG: Using publishable key or incorrect secret
STRIPE_WEBHOOK_SECRET=pk_live_xxxx # NEVER use publishable key STRIPE_WEBHOOK_SECRET=whsec_test_xxxx # test key in production✅ CORRECT: Exact secret from Dashboard → Webhooks → Click endpoint
STRIPE_WEBHOOK_SECRET=whsec_live_abcdef1234567890 # Copy from Dashboard exactly ```---
Why This Happens
1. Middleware order matters: bodyParser.json() or express.json() converts raw request body to JavaScript object. Stripe's signature verification requires the exact raw bytes that were signed.
2. Wrong environment variable: Copy-pasting the wrong secret from Dashboard (test vs. live, or wrong endpoint).
3. Request body modification: Logging middleware, authentication middleware, or custom parsers that touch req.body before webhook handler.
4. Version confusion: Stripe.js v3+ changed signature verification behavior. [We've documented the Node SDK update process](/?guide=stripe-sdk-migration).
---
Still broken? Check these too
1. Webhook secret mismatch between environments – Verify you copied the secret for the correct environment (test vs. live) and correct endpoint URL from Stripe Dashboard.
2. Request signing timestamp too old – Stripe rejects signatures older than 5 minutes by default. Check server time sync: date && curl -I https://www.stripe.com/ to verify server clock.
3. Raw body middleware not applied correctly – If using frameworks like NestJS or Express with custom middleware, ensure express.raw({type: 'application/json'}) wraps the webhook route before any JSON parsing. [See middleware ordering guide](/?guide=express-middleware-order).
---
Verification Checklist
whsec_ (not sk_, pk_, or rk_)express.raw() on webhook route only, not globallystripe-signature in request headers---
Official Resources
---
Found a different variation? Drop it in the comments – Stripe's webhook auth behavior varies by SDK version and framework. Help us build a complete troubleshooting resource.