Stripe: webhook signature verification failing [2026 fix]
Webhook signature verification fails when endpoint secret is missing, misconfigured, or raw body isn't passed to verification function.
Stripe: Webhook Signature Verification Failing [2026 Fix]
TL;DR
Cause: Your code is passing the parsed JSON body instead of the raw request body toconstructEvent(), or your endpoint secret is undefined/wrong.
Fix: Use req.rawBody (or equivalent raw buffer) and verify STRIPE_WEBHOOK_SECRET is set in environment variables.---
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 id: whsvc_1234567890 ```
``` No signatures found matching the expected signature for payload. Webhook signing secret does not match. ```
``` this.signature === undefined at Object.verifyHeaderAndExtractTimestamp ```
``` stripe.webhooks.constructEvent is not a function (when using incorrect import) ```
---
Broken Code vs. Fixed Code
❌ BROKEN: Passing Parsed Body
```javascript
// Express endpoint
app.post('/webhook', (req, res) => {
const payload = req.body; // ← WRONG: Already parsed JSON
const sig = req.headers['stripe-signature'];
try {
// This fails because Stripe signs the raw bytes, not parsed JSON
const event = stripe.webhooks.constructEvent(
payload,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
res.status(400).send(Webhook Error: ${err.message});
}
});
```
✅ FIXED: Using Raw Body
```javascript // Express with raw body middleware const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
// CRITICAL: Raw body for Stripe webhooks ONLY
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const payload = req.body; // Now this IS the raw buffer
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(
payload,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
// Process event safely
console.log(✓ Event verified: ${event.type});
res.status(200).json({received: true});
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
res.status(400).send(Webhook Error: ${err.message});
}
});
// Regular JSON parsing for other endpoints app.use(express.json()); ```
---
Environment & Config Checklist
Verify your .env file contains:
```bash STRIPE_WEBHOOK_SECRET=whsvc_1234567890abcdefghijklmnop STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxx ```
Verify secret is loaded:
```javascript if (!process.env.STRIPE_WEBHOOK_SECRET) { throw new Error('STRIPE_WEBHOOK_SECRET not set in environment'); } ```
Verify you're using the correct secret (not your signing secret, not API key). Get it from [Stripe Dashboard → Developers → Webhooks → Endpoint Details](https://dashboard.stripe.com/webhooks).
---
Framework-Specific Solutions
Next.js API Routes
```javascript // pages/api/webhooks/stripe.js export const config = { api: { bodyParser: false, // Disable default parser }, };
export default async function handler(req, res) {
const buf = await new Promise((resolve, reject) => {
let data = '';
req.on('data', chunk => { data += chunk; });
req.on('end', () => resolve(Buffer.from(data)));
req.on('error', reject);
});
const sig = req.headers['stripe-signature'];
try {
const event = stripe.webhooks.constructEvent(buf, sig, process.env.STRIPE_WEBHOOK_SECRET);
res.status(200).json({received: true});
} catch (err) {
res.status(400).send(Webhook Error: ${err.message});
}
}
```
FastAPI/Python
```python from fastapi import Request import stripe
stripe.api_key = os.getenv('STRIPE_SECRET_KEY') endpoint_secret = os.getenv('STRIPE_WEBHOOK_SECRET')
@app.post('/webhook') async def webhook_received(request: Request): body = await request.body() # Raw bytes sig_header = request.headers.get('stripe-signature') try: event = stripe.Webhook.construct_event(body, sig_header, endpoint_secret) return {'status': 'success'} except stripe.error.SignatureVerificationError: return {'error': 'Invalid signature'}, 403 ```
---
Still Broken? Check These Too
1. Timestamp Tolerance: Stripe rejects signatures older than 5 minutes. Verify your server clock is synced. Run ntpdate -s time.nist.gov on Linux or check System Preferences → Date & Time on macOS.
2. Multiple Webhook Endpoints: You might have registered the webhook URL twice with different secrets, or you're testing against staging while code points to production. Double-check stripe.webhooks.constructEvent() receives the endpoint secret, not your API key.
3. Middleware Order Matters: If using multiple middleware, ensure express.raw() is before express.json() on the webhook route. Global express.json() will parse the body before your webhook handler runs. See [Express middleware guide](/?guide=middleware-order).
---
Test Your Webhook Locally
```bash
Use Stripe CLI to forward webhooks
stripe listen --forward-to localhost:3000/webhook stripe trigger payment_intent.succeeded ```This bypasses signature verification issues during development.
---
Version Notes
constructEvent() signature unchanged.---
Official Documentation
[Stripe Webhook Signature Verification](https://stripe.com/docs/webhooks/signatures)
Found a different variation? Drop it in the comments.