Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch caused by wrong endpoint secret or missing raw request body in verification. Use correct secret from Dashboard and pass unparsed body to verify().
Stripe: Webhook Signature Verification Failing [2026 Fix]
TL;DR
Cause: You're verifying with the wrong endpoint secret OR passing JSON-parsed body instead of raw request buffer to stripe.webhooks.constructEvent().
Fix: Use the live/test secret matching your webhook URL from Stripe Dashboard, and always pass req.rawBody (bytes) not req.body (parsed JSON).
---
Exact Console Errors
Here are real error messages you're seeing at 2am:
``` Error: No signatures found matching the expected signature for payload. ```
``` Stripe signature verification failed: Webhook signature does not match the one provided. Webhook id: whsec_... ```
```
Fatal error in action POST /webhooks/stripe: Error: Webhook signature does not match the one provided. Webhook id: whsec_1O8x7pXxxx. Received signature does not match any of the configured signing secrets
```
``` ReferenceError: req.rawBody is undefined ```
``` SyntaxError: Cannot read property 'type' of undefined at Object.<anonymous> (webhooks.js:42:15) ```
---
Broken Code → Fixed Side-by-Side
❌ BROKEN: Using parsed body
```javascript
app.post('/webhooks/stripe', (req, res) => {
const sig = req.headers['stripe-signature'];
try {
// WRONG: req.body is already parsed JSON
const event = stripe.webhooks.constructEvent(
req.body, // ← PROBLEM: This is a JavaScript object, not raw bytes
sig,
process.env.STRIPE_ENDPOINT_SECRET
);
console.log('Webhook received:', event.type);
res.json({received: true});
} catch (err) {
console.error('Webhook Error:', err.message);
res.status(400).send(Webhook Error: ${err.message});
}
});
```
✅ FIXED: Using raw body buffer
```javascript
app.post('/webhooks/stripe', (req, res) => {
const sig = req.headers['stripe-signature'];
try {
// CORRECT: req.rawBody is the unparsed request buffer
const event = stripe.webhooks.constructEvent(
req.rawBody, // ← FIX: Raw request body as Buffer/string
sig,
process.env.STRIPE_ENDPOINT_SECRET
);
console.log('Webhook received:', event.type);
res.json({received: true});
} catch (err) {
console.error('Webhook Error:', err.message);
res.status(400).send(Webhook Error: ${err.message});
}
});
```
❌ BROKEN: Express middleware ordering
```javascript app.use(express.json()); // Parses body FIRST app.post('/webhooks/stripe', handleStripeWebhook); // Can't access raw body ```
✅ FIXED: Raw body before JSON parsing
```javascript // Stripe webhooks must come BEFORE JSON parser app.post('/webhooks/stripe', express.raw({type: 'application/json'}), handleStripeWebhook); app.use(express.json()); // Parse body for other routes ```
❌ BROKEN: Wrong endpoint secret
```javascript // Using signing secret instead of endpoint secret const secret = process.env.STRIPE_API_KEY; // ← WRONG const event = stripe.webhooks.constructEvent(body, sig, secret); ```
✅ FIXED: Correct endpoint secret from Dashboard
```javascript // Use whsec_* secret from Webhooks page, NOT your API key const secret = process.env.STRIPE_WEBHOOK_SECRET; // whsec_xxx const event = stripe.webhooks.constructEvent(body, sig, secret); ```
---
Step-by-Step Verification
1. Check Dashboard secret: Go to stripe.com → Developers → Webhooks → click your endpoint → copy "Signing secret" (starts with whsec_)
2. Verify environment variable: echo $STRIPE_WEBHOOK_SECRET should output whsec_xxxxx, NOT your API key
3. Test locally: Use Stripe CLI: stripe listen --forward-to localhost:3000/webhooks/stripe
4. Trigger test event: stripe trigger payment_intent.succeeded in another terminal
5. Check logs: You should see "Webhook received: payment_intent.succeeded"
I'm uncertain about version-specific differences in older versions of @stripe/stripe-js (pre-2022); those may require JSON.stringify(req.rawBody) instead of passing raw Buffer directly. Test this in your environment.
---
Still Broken? Check These Too
1. [Stripe webhook endpoint not triggering at all?](/?guide=stripe-webhook-not-called) — Verify URL is publicly accessible, HTTPS, and matches Webhook settings exactly 2. [HMAC signature algorithm mismatch](/?guide=stripe-webhook-hmac) — Some frameworks automatically base64-encode; ensure you're comparing raw bytes 3. [Webhook secret rotated but old code running](/?guide=stripe-secret-rotation) — Check if multiple app instances exist; restart all processes after env var update
---
Official Resources
---
Found a different variation? Drop it in the comments — If you hit a different signature verification error (e.g., with Fastify, Django, or Node v18+), share it and we'll add it to this guide.