Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch: endpoint secret misconfigured or request body modified before verification. Use raw body buffer, never parsed JSON.
Stripe: Webhook Signature Verification Failing [2026 Fix]
TL;DR
Cause: Your endpoint is verifying the signature against parsed JSON instead of the raw request body, or your webhook endpoint secret is mismatched between Stripe dashboard and your code.Fix: Pass the raw unparsed request body to stripe.webhooks.constructEvent() and verify your endpoint secret matches exactly in your .env file.
---
Real Console Error Messages
``` Error: No signatures found matching the expected signature for payload. ```
``` Stripe.error.SignatureVerificationError: No signatures found matching the expected signature for payload. Webhook signing secret: whsec_... ```
``` Error: Unable to extract timestamp and signatures from header ```
``` SignatureVerificationError: No signatures found matching the expected signature for payload. [402] Request body does not match signature ```
``` Error: Invalid request: missing or invalid stripe-signature header ```
---
Broken Code vs. Exact Fix
❌ BROKEN (Express.js)
```javascript const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/webhook', express.json(), (req, res) => {
const sig = req.headers['stripe-signature'];
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
try {
// WRONG: Body is already parsed JSON
const event = stripe.webhooks.constructEvent(
req.body, // ← PROBLEM: Should be raw buffer
sig,
endpointSecret
);
res.json({received: true});
} catch (err) {
res.status(400).send(Webhook Error: ${err.message});
}
});
```
✅ FIXED (Express.js)
```javascript const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
// Use raw body parser ONLY for webhook endpoint
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
// Verify endpointSecret exists
if (!endpointSecret) {
return res.status(400).send('Webhook secret not configured');
}
try {
// CORRECT: Pass raw buffer
const event = stripe.webhooks.constructEvent(
req.body, // ← Raw Buffer object
sig,
endpointSecret
);
// Handle specific events
switch(event.type) {
case 'payment_intent.succeeded':
console.log('Payment succeeded:', event.data.object.id);
break;
case 'charge.failed':
console.log('Charge failed:', event.data.object.id);
break;
}
res.json({received: true});
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
res.status(400).send(Webhook Error: ${err.message});
}
});
```
Common Framework-Specific Setups
Next.js API Routes: ```javascript import { buffer } from 'micro';
export const config = { api: { bodyParser: false } };
export default async (req, res) => { if (req.method === 'POST') { const buf = await buffer(req); const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( buf, sig, process.env.STRIPE_WEBHOOK_SECRET ); // Process event... } }; ```
Django: ```python from django.views.decorators.csrf import csrf_exempt from django.http import JsonResponse import stripe
@csrf_exempt def stripe_webhook(request): payload = request.body # Raw bytes sig_header = request.META.get('HTTP_STRIPE_SIGNATURE') endpoint_secret = os.environ['STRIPE_WEBHOOK_SECRET'] try: event = stripe.Webhook.construct_event( payload, sig_header, endpoint_secret ) except ValueError: return JsonResponse({'error': 'Invalid payload'}, status=400) ```
---
Verification Checklist
1. Endpoint Secret Match
- Go to Stripe Dashboard → Developers → Webhooks
- Copy exact whsec_... value
- Paste into your .env file: STRIPE_WEBHOOK_SECRET=whsec_live_...
- Restart your server after .env change
2. Raw Body Requirement
- Signature verification depends on byte-for-byte matching
- Any JSON parsing before verification breaks the signature
- Use express.raw() or framework equivalent
3. Middleware Order
- Webhook route must come BEFORE global express.json() middleware
```javascript
app.post('/webhook', express.raw({type: 'application/json'}), handler);
app.use(express.json()); // Other routes
```
---
Still Broken? Check These Too
1. [Stripe API Key Format](/guide=stripe-key-validation) — Verify you're using sk_live_ (production) or sk_test_ (test mode), not publishable key
2. Webhook Endpoint Not Registered — In Stripe Dashboard, confirm webhook URL is exactly https://yourdomain.com/webhook (no trailing slash difference)
3. [Request Body Logging Issues](/guide=stripe-webhook-debugging) — Never log req.body after parsing; log the raw signature header instead
4. Timestamp Tolerance — Stripe webhooks fail if server time is >5 minutes out of sync; check system clock
5. Load Balancer/Proxy Modifying Body — Some proxies recompress or modify the request body; verify raw bytes match what Stripe sent
---
Official Documentation
[Stripe Webhook Signature Verification Docs](https://stripe.com/docs/webhooks/signatures)
---
Found a different variation? Drop it in the comments.