Stripe: webhook signature verification failing [2026 fix]
Webhook signature verification fails when endpoint_secret is missing or mismatched. Copy the correct signing secret from Dashboard and pass it to verifyHeader().
Stripe: webhook signature verification failing [2026 fix]
TL;DR
Cause: Your webhook endpoint is using the wrong or missingendpoint_secret (signing secret from Stripe Dashboard).
Fix: Copy the correct signing secret from Stripe Dashboard → Developers → Webhooks → select endpoint → "Signing secret" and pass it to stripe.webhooks.constructEvent().---
Real Console Error Messages
These are the exact errors you'll see at 2am:
``` Error: No signatures found matching the expected signature for payload. computed signature=<hash>, attempted signature versions=[v1] ```
``` StripeSignatureVerificationError: No signatures found matching the expected signature for payload at Function.constructEvent (/node_modules/stripe/lib/webhooks.js:104:15) ```
``` Webhook Error: error decoding event request body: json: cannot unmarshal string into Go struct field Event.created of type int64 ```
``` stripe.error.SignatureVerificationError: No signatures found matching the expected signature for payload ```
``` Fatal error: HMAC verification failed. Expected signature header 'stripe-signature' not found. ```
---
Broken Code vs. Exact Fix
❌ BROKEN (Most Common)
```javascript // webhook.js - WRONG const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { // ❌ Missing endpoint_secret parameter const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], // MISSING: endpoint_secret here! ); res.json({received: true}); }); ```
✅ FIXED
```javascript // webhook.js - CORRECT const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { // ✅ Pass the signing secret from Stripe Dashboard const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET // ← Copy from Dashboard ); res.json({received: true}); }); ```
❌ BROKEN (Wrong Secret Used)
```javascript // ❌ Using publishable key instead of webhook signing secret const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_PUBLISHABLE_KEY // WRONG! This is not a signing secret ); ```
✅ FIXED
```javascript // ✅ Use ONLY the webhook endpoint's signing secret const event = stripe.webhooks.constructEvent( req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET // From: Dashboard → Webhooks → select endpoint → "Signing secret" ); ```
❌ BROKEN (String Body Instead of Raw)
```javascript // ❌ Using express.json() instead of express.raw() app.post('/webhook', express.json(), (req, res) => { // This converts body to string, breaking HMAC verification const event = stripe.webhooks.constructEvent( req.body, // ❌ Already parsed, signature fails req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); }); ```
✅ FIXED
```javascript // ✅ MUST use express.raw() to preserve raw bytes for HMAC app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const event = stripe.webhooks.constructEvent( req.body, // ✅ Still raw buffer, HMAC verifies req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET ); }); ```
---
How to Find Your Signing Secret
1. Go to [Stripe Dashboard](https://dashboard.stripe.com)
2. Navigate: Developers → Webhooks
3. Find your endpoint URL in the list
4. Click on it
5. Scroll to Signing secret section
6. Click Reveal
7. Copy the secret starting with whsec_
8. Add to .env:
```
STRIPE_WEBHOOK_SECRET=whsec_test_xxxxxxxxxxxx
```
9. Restart your server
10. Test with stripe trigger payment_intent.succeeded --skip-confirmation
---
Version-Specific Notes
Stripe Node.js SDK (v8.0.0+): The constructEvent() method signature is consistent. However, we cannot guarantee behavior in versions prior to v7.0.0—if you're on an older version, upgrade with npm install stripe@latest.
Express.js: The express.raw() middleware requirement applies to Express 4.16.0+. Older versions may need body-parser middleware instead.
---
Still broken? Check these too
1. Webhook endpoint is offline: Stripe retries for 3 days. Check server logs. If your endpoint crashed, [related guide: debugging Express 500 errors](/?guide=express-errors).
2. Multiple webhook endpoints configured: You have one signing secret *per endpoint*. Verify you're using the secret matching your exact URL, not another endpoint's secret.
3. Body middleware conflict: Ensure no global express.json() middleware processes the webhook route *before* express.raw(). Move webhook route to top of middleware stack or use route-specific middleware: [related: middleware ordering](/?guide=middleware-order).
4. Stripe CLI testing without secret: If testing locally with stripe listen, the CLI auto-generates a signing secret. When you run stripe listen --forward-to localhost:3000/webhook, it prints the secret—use that exact value.
5. Environment variable not loaded: Confirm process.env.STRIPE_WEBHOOK_SECRET is populated by logging it (never log in production). Check .env file format: no quotes, no spaces.
---
Official Documentation
[Stripe Webhook Signing (Official Docs)](https://stripe.com/docs/webhooks/signatures)
---
Found a different variation? Drop it in the comments below—we'll add it to this guide.