Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch due to raw body parsing or endpoint secret misconfiguration—use raw request body and verify secret matches dashboard.
Stripe: Webhook Signature Verification Failing [2026 Fix]
TL;DR
Cause: You're passing parsed JSON instead of raw request body to signature verification, or your endpoint secret is wrong. Fix: Always verify signatures againstreq.rawBody (not stringified JSON) and confirm your endpoint secret matches your Stripe dashboard.---
Real Console Error Messages
``` Error: No signatures found matching the expected signature for payload. ```
``` StripeSignatureVerificationError: No signatures found matching the expected signature for payload. Webhook secret (***_test_abc123) may be invalid. ```
``` Error: Timestamp outside the tolerance zone (current timestamp: 1735689600, webhook timestamp: 1735689400, tolerance: 300 seconds) ```
``` TypeError: Cannot read property 'signature' of undefined ```
``` Error: Invalid signature. Expected sig=..., got sig=... ```
---
Broken Code vs. Fixed Code
Issue 1: Parsing JSON Before Verification ❌ → ✅
BROKEN: ```javascript app.post('/webhook', express.json(), (req, res) => { // WRONG: body is already parsed const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( JSON.stringify(req.body), // ❌ This is double-encoded sig, process.env.STRIPE_WEBHOOK_SECRET ); res.json({ received: true }); }); ```
FIXED: ```javascript app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { // CORRECT: body is raw Buffer const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( req.body, // ✅ Pass raw buffer directly sig, process.env.STRIPE_WEBHOOK_SECRET ); res.json({ received: true }); }); ```
Issue 2: Wrong Endpoint Secret ❌ → ✅
BROKEN: ```javascript const stripe = require('stripe')(process.env.STRIPE_API_KEY); // Using API key instead of webhook secret const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_API_KEY // ❌ WRONG ); ```
FIXED: ```javascript const stripe = require('stripe')(process.env.STRIPE_API_KEY); // Use the whsec_* secret from Stripe dashboard → Webhooks const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET // ✅ Starts with whsec_ ); ```
Issue 3: Express Global Middleware Conflict ❌ → ✅
BROKEN: ```javascript // Global middleware parses ALL requests app.use(express.json());
app.post('/webhook', (req, res) => { // req.body is already parsed—signature fails const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); }); ```
FIXED: ```javascript // Parse JSON AFTER webhook route (order matters) app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); });
// Now parse everything else app.use(express.json()); app.use(express.urlencoded({extended: true})); ```
---
Verification Checklist
1. Endpoint Secret: Go to Stripe Dashboard → Developers → Webhooks → Click your endpoint → Copy secret (starts with whsec_)
2. Environment Variable: echo $STRIPE_WEBHOOK_SECRET should show whsec_* not your API key
3. Request Body: Verify req.body is a Buffer, not a parsed object
4. Signature Header: Confirm header exists: req.headers['stripe-signature'] should print a value
5. Test Event: Use stripe trigger charge.succeeded --api_key $STRIPE_API_KEY to send test webhook
---
Still Broken? Check These Too
1. Timestamp Tolerance Exceeded
Stripe rejects signatures older than 5 minutes. If your server clock is wrong, webhooks fail. Run date -u and sync with NTP if skewed. Increase tolerance cautiously: stripe.webhooks.constructEvent(body, sig, secret, 600) extends to 10 minutes.
2. Multiple Webhook Endpoints
You registered the same endpoint twice in Stripe Dashboard, or you deployed to staging and production with different secrets. Verify which secret matches your current deployment: grep STRIPE_WEBHOOK_SECRET .env.
3. Reverse Proxy Stripping Raw Body
Nginx/AWS ALB might buffer and re-encode the request body. Ensure proxy_request_buffering off; (Nginx) or equivalent, and that the raw body reaches your Node process unchanged.
---
Related Guides
---
Official Resources
---
Version Notes
These examples use Stripe Node.js SDK v13+ (2024–2026). Older versions (<v12) require stripe.webhooks.constructEvent(rawBody, sig, secret) without changes. Verify your version: npm list stripe.
---
Found a different variation? Drop it in the comments—webhook issues have many faces.