Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch—verify endpoint secret format and raw request body handling in your webhook handler.
Stripe: Webhook Signature Verification Failing [2AM Fix]
TL;DR
Cause: Your webhook endpoint is using the Dashboard API key instead of the endpoint-specific signing secret, or you're parsing the request body before signature verification.
Fix: Use whsec_ or whsec_test_ endpoint secret from Webhook settings, verify the raw body before any JSON parsing, and ensure your framework isn't auto-parsing the body stream.
---
Real Console Error Messages
You'll likely see one of these exact errors:
``` Error: No signatures found matching the expected signature for payload. Discard webhook and investigate further if necessary. ```
``` StripeSignatureVerificationError: Unable to extract timestamp and signatures from header ```
``` Signature verification failed. Webhook signature does not match. ```
``` Error: Invalid signature. Expected sig, but got undefined ```
``` WebhookEndpointNotFound: Could not find webhook endpoint secret for endpoint_id=we_1234567890 ```
---
Broken Code vs. Exact Fix
Problem 1: Using Wrong Secret Type
Broken ❌ ```javascript const stripe = require('stripe')(process.env.STRIPE_API_KEY);
app.post('/webhook', express.json(), (req, res) => { const sig = req.headers['stripe-signature']; // WRONG: Using API key instead of endpoint secret const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_API_KEY // ← THIS IS WRONG ); res.json({received: true}); }); ```
Fixed ✓ ```javascript const stripe = require('stripe')(process.env.STRIPE_API_KEY);
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; // CORRECT: Using endpoint-specific signing secret const event = stripe.webhooks.constructEvent( req.body, // Must be raw Buffer, not parsed JSON sig, process.env.STRIPE_WEBHOOK_SECRET // whsec_test_... or whsec_... ); res.json({received: true}); }); ```
Problem 2: Body Parsed Before Verification
Broken ❌ ```javascript app.use(express.json()); // ← Parses body globally
app.post('/webhook', (req, res) => {
const sig = req.headers['stripe-signature'];
try {
// req.body is already a JavaScript object, not raw bytes
const event = stripe.webhooks.constructEvent(
req.body, // ← WRONG: Already parsed
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(Webhook Error: ${err.message});
}
});
```
Fixed ✓
```javascript
// Apply express.json() AFTER webhook route
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
try {
// req.body is a Buffer—signature verification works
const event = stripe.webhooks.constructEvent(
req.body, // ← CORRECT: Raw Buffer
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
// Now handle event.type
switch(event.type) {
case 'payment_intent.succeeded':
console.log('Payment succeeded:', event.data.object.id);
break;
}
} catch (err) {
return res.status(400).send(Webhook Error: ${err.message});
}
res.json({received: true});
});
app.use(express.json()); // Apply AFTER webhook route ```
Problem 3: Environment Variable Not Set
Broken ❌ ```javascript // .env file missing or has wrong value STRIPE_WEBHOOK_SECRET=sk_test_1234... // ← Wrong prefix ```
Fixed ✓ ```bash
.env file must have endpoint secret, not API key
STRIPE_API_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_test_...Verify in Stripe Dashboard:
Developers → Webhooks → [Your Endpoint] → Signing Secret
```---
Still Broken? Check These Too
1. Timestamp Outside 5-Minute Window
- Stripe rejects signatures older than 5 minutes by default
- Check server time sync: date (should match NTP)
- Stripe uses t= in the signature header—if your server clock is off, verification fails
- See [webhook timestamp validation guide](/?guide=stripe-timestamp-window)
2. Webhook Endpoint URL Not Matching
- Stripe Dashboard shows https://example.com/webhook
- Your code is actually at https://example.com/webhooks or https://api.example.com/webhook
- Verify in Stripe Dashboard → Developers → Webhooks → edit endpoint URL
- See [webhook routing issues](/?guide=webhook-endpoint-mismatch)
3. Firewall/Proxy Modifying Request Body
- API gateway, load balancer, or reverse proxy adding headers/encoding
- Signature is computed on original body—any modification breaks verification
- Solution: Disable body transformation in middleware (disable gzip, content-type rewrites)
- Log req.rawBody and compare to signature calculation
---
Quick Verification Checklist
express.raw({type: 'application/json'}) NOT express.json()STRIPE_WEBHOOK_SECRET starts with whsec_date output within ±5 seconds of NTP)req.body before signature verificationstripe npm package (≥9.0.0 as of 2026)---
Official Resources
[Stripe Webhook Documentation](https://stripe.com/docs/webhooks) [Stripe Node.js SDK - webhooks.constructEvent](https://stripe.com/docs/api/node#construct_webhook_event)
---
Found a different variation? Drop it in the comments.