Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch: endpoint secret doesn't match request signature. Verify you're using raw request body, not parsed JSON.
Stripe: Webhook Signature Verification Failing [2026 Fix]
TL;DR
Cause: You're passing parsed JSON to signature verification instead of the raw request body.
Fix: Use express.raw({type: 'application/json'}) middleware BEFORE body parsing for that route.
---
Common Error Messages
Here are exact console outputs you might see:
``` Error: No signatures found matching the expected signature for payload. ```
``` Stripe error [ERR_SIGNATURE_VERIFICATION_FAILED]: Webhook signature verification failed, possibly due to changed API version or endpoint secret ```
``` error: 'sig_' does not match webhook signature 'v1,t=1234567890,v1=abc123def456' ```
``` SignatureVerificationError: Unable to extract timestamp and signatures from header 'stripe-signature' ```
``` WebhookEndpointDoesNotExist: Webhook endpoint secret is invalid or does not exist ```
---
Broken Code vs. Fix
Problem: Body Already Parsed
```javascript // ❌ BROKEN const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
// This parses ALL requests including webhooks app.use(express.json());
app.post('/webhook', (req, res) => {
const sig = req.headers['stripe-signature'];
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
try {
// req.body is already stringified JSON object
// Stripe can't verify signature on parsed object
const event = stripe.webhooks.constructEvent(
req.body,
sig,
webhookSecret
);
res.json({received: true});
} catch (error) {
res.status(400).send(Webhook Error: ${error.message});
}
});
```
Solution: Raw Body for Webhook Route
```javascript // ✅ FIXED const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
// Parse JSON for all routes EXCEPT webhooks app.use(express.json());
// Use raw body ONLY for webhook route
app.post(
'/webhook',
express.raw({type: 'application/json'}),
(req, res) => {
const sig = req.headers['stripe-signature'];
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
try {
// req.body is now Buffer (raw bytes)
// Stripe can verify signature correctly
const event = stripe.webhooks.constructEvent(
req.body,
sig,
webhookSecret
);
// Handle event
switch (event.type) {
case 'payment_intent.succeeded':
console.log('Payment succeeded:', event.data.object.id);
break;
case 'payment_intent.payment_failed':
console.log('Payment failed:', event.data.object.id);
break;
}
res.json({received: true});
} catch (error) {
res.status(400).send(Webhook Error: ${error.message});
}
}
);
app.listen(3000); ```
Key difference: The raw middleware must come AFTER app.use(express.json()) but apply only to the webhook endpoint.
---
Quick Verification Checklist
1. Webhook Secret: Confirm STRIPE_WEBHOOK_SECRET matches the secret shown in Stripe Dashboard → Developers → Webhooks → your endpoint's signing secret (starts with whsec_)
2. Raw Body Middleware: Ensure express.raw({type: 'application/json'}) is applied to your webhook route before signature verification
3. Request Body Type: Log typeof req.body — should be object for Buffer, not string
4. Test Webhook: Use Stripe's test event in Dashboard (Webhooks → Send test event) rather than manual curl requests
---
Still Broken? Check These Too
1. Proxy/Reverse Proxy Buffering Raw Body
Some reverse proxies (nginx, CloudFlare Workers) buffer and re-encode the request body. Verify: Enable request logging to see if body content changes between Stripe's send and your receipt. Check proxy documentation for raw body passthrough settings.2. Multiple Webhook Endpoints with Wrong Secret
You might have registered the webhook endpoint in Dashboard with one secret, but your code references a different secret from.env. Verify: Log both webhookSecret variable and Stripe Dashboard value character-by-character.3. Framework Auto-Parsing Middleware
Other middleware (body-parser, fastify, Next.js API routes) may auto-parse before reaching your handler. Verify: Disable middleware selectively and test. For Next.js, see [Stripe Next.js troubleshooting](/?guide=nextjs-stripe).---
Environment Setup
```bash
.env
STRIPE_SECRET_KEY=sk_live_... STRIPE_WEBHOOK_SECRET=whsec_... ```Note: If uncertain whether your specific Stripe SDK version requires additional configuration beyond raw body handling, check the [official Stripe webhook docs](https://stripe.com/docs/webhooks/signatures) for your language.
---
Debugging Tips
Add temporary logging before verification:
```javascript console.log('Body type:', typeof req.body); console.log('Signature header:', req.headers['stripe-signature']); console.log('Webhook secret set:', !!process.env.STRIPE_WEBHOOK_SECRET); ```
If signature header is missing entirely, Stripe's test event didn't reach your endpoint at all—check network routing and firewall rules.
---
Related Resources
---
Found a different variation? Drop it in the comments — especially if you're using a different framework (Django, Rails, Spring) or encountered this with a proxy setup.