Stripe: webhook signature verification failing [2026 fix]
Webhook signature mismatch due to raw body tampering or wrong endpoint secret—use raw request body and correct secret key immediately.
Stripe: Webhook Signature Verification Failing [2am SOS]
TL;DR
Cause: Your webhook handler is verifying against a parsed/stringified body instead of the raw request stream, OR you're using the wrong endpoint secret. Fix: Pass the raw unparsed request body buffer tostripe.webhooks.constructEvent() and verify you're using your *endpoint's* secret (not your API key).---
Real Console Error Messages
``` 1. Error: No signatures found matching the expected signature for payload.
2. StripeSignatureVerificationError: Unable to extract timestamp and signatures from header stripe-signature
3. Error: Webhook Error: invalid payload signature
4. TypeError: Cannot read property 'id' of undefined (downstream, triggered by failed verification)
5. 403 Forbidden - Stripe webhook signature verification failed ```
---
The Problem: Side-by-Side Code Comparison
❌ BROKEN (Most Common)
```javascript // Express.js with body-parser (DEFAULT SETUP) const express = require('express'); const bodyParser = require('body-parser'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express(); // THIS IS THE KILLER: body-parser JSON middleware parses the body app.use(bodyParser.json());
app.post('/webhook', (req, res) => { // req.body is NOW a parsed JavaScript object, NOT raw bytes // Stripe's signature is computed against the original raw bytes // These don't match → verification fails const event = stripe.webhooks.constructEvent( req.body, // 🚨 WRONG: This is parsed JSON, not raw body req.headers['stripe-signature'], process.env.STRIPE_ENDPOINT_SECRET ); res.sendStatus(200); }); ```
✅ FIXED (Correct Implementation)
```javascript // Express.js - CORRECT approach const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
// CRITICAL: Express raw body middleware BEFORE other parsers app.use(express.raw({type: 'application/json'}));
// NOW apply JSON parsing to everything EXCEPT /webhook app.use((req, res, next) => { if (req.path === '/webhook') { return next(); } express.json()(req, res, next); });
app.post('/webhook', (req, res) => { // req.body is now a Buffer containing raw bytes // This matches Stripe's signature computation const sig = req.headers['stripe-signature']; let event; try { event = stripe.webhooks.constructEvent( req.body, // ✅ CORRECT: Raw buffer sig, process.env.STRIPE_ENDPOINT_SECRET // ✅ Use ENDPOINT secret, not API key ); } catch (err) { console.error('Webhook Error:', err.message); return res.sendStatus(400); }
// Process event safely
console.log(Received event: ${event.type});
res.sendStatus(200);
});
```
Alternative: Fastify Fix
```javascript const fastify = require('fastify'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = fastify();
// Fastify: Tell it NOT to parse webhook route app.register(require('@fastify/raw-body'), { routes: ['/webhook'] });
app.post('/webhook', async (request, reply) => { // request.rawBody contains the raw bytes const event = stripe.webhooks.constructEvent( request.rawBody, request.headers['stripe-signature'], process.env.STRIPE_ENDPOINT_SECRET ); reply.code(200).send({ ok: true }); }); ```
---
Critical Checklist
1. Environment Variable Verification: ```bash # In your .env or hosting platform: STRIPE_ENDPOINT_SECRET=whsec_test_4eC39HqLyjWDarhtT657j8saw # NOT your API key STRIPE_SECRET_KEY=sk_test_4eC39HqLyjWDarhtT657j8saw ``` Get the endpoint secret from [Stripe Dashboard → Webhooks → Your Endpoint → Signing Secret]
2. Confirm raw body is Buffer: ```javascript app.post('/webhook', (req, res) => { console.log('Body type:', typeof req.body, 'Is Buffer:', Buffer.isBuffer(req.body)); // Should output: "Body type: object Is Buffer: true" }); ```
3. Middleware Order Matters:
- express.raw() MUST come before express.json() for this route
---
Still Broken? Check These Too
1. [Webhook Endpoint Not Registered Properly](/?guide=stripe-webhook-registration) — Verify endpoint exists in Dashboard and is enabled. Test button works but curl fails? Check firewall/network ACLs blocking Stripe IP ranges (35.184.0.0/13).
2. [Stripe.js vs @stripe/stripe-js Version Mismatch](/?guide=stripe-version-conflict) — Using stripe npm package v8 with Node 14? Explicit compatibility check required. Run npm ls stripe and cross-reference [official SDK changelog](https://github.com/stripe/stripe-node/releases).
3. Clock Skew Issues — Stripe rejects signatures older than 5 minutes. If your server time is off by >300s, verification fails silently. Check: date command on server vs NTP.
---
Official References
---
Found a different variation? Drop it in the comments — Common setups: Next.js API routes, AWS Lambda, Google Cloud Functions, or custom request body buffering strategies. Your fix might save someone 2 hours of debugging at 2am.