Stripe: webhook signature verification failing [2026 fix]
Webhook signature verification fails when endpoint secret is missing, misconfigured, or timestamp tolerance exceeded; verify secret matches dashboard and check request headers.
Stripe: Webhook Signature Verification Failing [2am Emergency Fix]
TL;DR
Cause: Your endpoint secret doesn't match Stripe's dashboard, or the request timestamp is outside the 5-minute tolerance window. Fix: Grab the correct signing secret from Stripe Dashboard → Developers → Webhooks, and ensure your server clock is synced.---
Exact Error Messages You're Seeing
``` Error: No signatures found matching the expected signature for payload. ```
``` Error: Timestamp outside the tolerance window. ```
``` UnhandledPromiseRejectionWarning: Error: Webhook signature verification failed at Object.constructEvent [as constructEvent] (/node_modules/stripe/lib/utils.js:238:19) ```
``` StripeSignatureVerificationError: No signatures found matching the expected signature for payload. (Webhook Signature Verification Failed) ```
``` warning: stripe signature verification failed, timestamp too old ```
---
The Problem: Side-by-Side Code Comparison
❌ Broken Code
```javascript const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const endpointSecret = 'whsec_test1234'; // HARDCODED - WRONG
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
endpointSecret // Using hardcoded/stale secret
);
console.log('✓ Webhook verified');
res.json({received: true});
} catch (err) {
console.error('Webhook Error:', err.message);
return res.status(400).send(Webhook Error: ${err.message});
}
});
```
✅ Fixed Code
```javascript const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET; // From env
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
req.body,
sig,
endpointSecret // Matches dashboard exactly
);
console.log('✓ Webhook verified for event:', event.type);
res.json({received: true});
} catch (err) {
if (err.message.includes('Timestamp')) {
console.error('Clock skew detected. Sync server time.');
} else if (err.message.includes('No signatures found')) {
console.error('Secret mismatch. Check STRIPE_WEBHOOK_SECRET env var.');
}
return res.status(400).send(Webhook Error: ${err.message});
}
});
```
---
Verification Checklist (Do This Now)
1. Confirm Your Endpoint Secret
example.com/webhook)whsec_)process.env.STRIPE_WEBHOOK_SECRET in your running process:2. Check Server Time Sync
Stripe rejects requests with timestamps >5 minutes old. If your server clock is behind:```bash
Linux/Mac
date # Should match within a minute of https://time.is/If wrong, sync:
sudo ntpdate -s time.nist.gov # Linux sudo sntp -sS time.nist.gov # macOS ```3. Verify Request Body Format
The raw body must be a Buffer, not a parsed JSON object:```javascript // WRONG - body already parsed app.post('/webhook', express.json(), (req, res) => { const event = stripe.webhooks.constructEvent(req.body, sig, secret); // ^ req.body is now an object, signature fails });
// RIGHT - body stays as Buffer app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const event = stripe.webhooks.constructEvent(req.body, sig, secret); // ^ req.body is Buffer, signature works }); ```
---
Still Broken? Check These Too
1. Environment Variable Not Loaded: Your .env file exists locally but production doesn't have STRIPE_WEBHOOK_SECRET set. Check your deployment platform (Heroku/AWS/etc.) for missing secrets: [Secrets management guide](/?guide=env-variables)
2. Multiple Endpoints, Wrong Secret: If you have staging and production webhooks, you're using the staging secret on production (or vice versa). Each endpoint has its own signing secret in the Dashboard—double-check the URL matches your environment.
3. Stripe Package Out of Date: If running stripe@<8.0.0, webhook verification behaves differently. Run npm list stripe and upgrade if necessary: npm install stripe@latest
4. Request Body Corruption: Reverse proxies (nginx, load balancers) may modify the raw body. Ensure your webhook endpoint is not behind middleware that alters the request before it reaches your handler. [Nginx webhook passthrough](/?guide=nginx-stripe)
---
Quick Deploy Fix (3 Steps)
```bash
1. Grab current secret from dashboard
STRIPE_WEBHOOK_SECRET='whsec_live_abc123...'2. Set in production
heroku config:set STRIPE_WEBHOOK_SECRET=$STRIPE_WEBHOOK_SECRETOR for AWS:
aws ssm put-parameter --name /stripe/webhook-secret --value whsec_live_abc1233. Restart
heroku dyno restart ```---
Version Notes
Stripe Node SDK: This guide applies tostripe@^8.0.0 through current 2026 releases. If using older versions (pre-2020), the constructEvent API differs—check official docs below.---
Official Resources
---
Found a different variation? Drop it in the comments—help the next person at 2am. 🚀