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: You're passing parsed JSON to verifySignatureHeader() instead of the raw request body string. Fix: Capture the raw body *before* JSON parsing, then pass that string to the verification function.

---

Real Console Error Messages

``` Error: No signatures found matching the expected signature for payload. ```

``` StripeSignatureVerificationError: No signatures found matching the expected signature for payload. Webhook id: evt_XXXXXXXXXXXXX ```

``` UnexpectedError: Webhook signing secret not found in environment ```

``` SignatureVerificationError: Unable to extract timestamp and signatures from header 'stripe-signature' ```

``` TypeError: Cannot read property 'verifySignatureHeader' of undefined ```

---

Broken Code vs. Fixed Code

Problem #1: Parsing Body Before Verification

BROKEN: ```javascript app.post('/webhook', express.json(), (req, res) => { const sig = req.headers['stripe-signature']; const secret = process.env.STRIPE_WEBHOOK_SECRET; try { // ❌ WRONG: req.body is already parsed JSON object const event = stripe.webhooks.constructEvent( req.body, // This is an object, not a string! sig, secret ); res.json({ received: true }); } catch (err) { res.status(400).send(Webhook error: ${err.message}); } }); ```

FIXED: ```javascript app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['stripe-signature']; const secret = process.env.STRIPE_WEBHOOK_SECRET; try { // ✅ CORRECT: req.body is raw Buffer/string const event = stripe.webhooks.constructEvent( req.body, // Buffer from express.raw() sig, secret ); res.json({ received: true }); } catch (err) { res.status(400).send(Webhook error: ${err.message}); } }); ```

Problem #2: Missing Environment Variable

BROKEN: ```javascript const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['stripe-signature']; // ❌ WRONG: Secret key used instead of webhook secret const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_SECRET_KEY // This is wrong! ); }); ```

FIXED: ```javascript require('dotenv').config();

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

if (!process.env.STRIPE_WEBHOOK_SECRET) { throw new Error('STRIPE_WEBHOOK_SECRET not set in environment'); }

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['stripe-signature']; // ✅ CORRECT: Dedicated webhook secret const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET // Different from API key! ); }); ```

Problem #3: Wrong Middleware Stack Order

BROKEN: ```javascript app.use(express.json()); // ❌ Parses all bodies globally app.post('/webhook', (req, res) => { // req.body is already parsed object const event = stripe.webhooks.constructEvent(req.body, sig, secret); }); ```

FIXED: ```javascript app.use(express.json()); // Safe for other routes

// ✅ CORRECT: Raw middleware ONLY for webhook route app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const event = stripe.webhooks.constructEvent(req.body, sig, secret); });

app.post('/api/charges', express.json(), (req, res) => { // This still gets parsed JSON }); ```

---

Verification Checklist

1. Environment Variable Set? ```bash echo $STRIPE_WEBHOOK_SECRET # Must output: whsec_live_XXXX or whsec_test_XXXX ```

2. Endpoint Secret Matches Dashboard? - Go to: [Stripe Dashboard](https://dashboard.stripe.com/webhooks) → Endpoints - Click your endpoint → "Signing secret" button - Verify exact match (including whsec_ prefix)

3. Test Locally? ```bash stripe listen --forward-to localhost:3000/webhook # Outputs: Ready! Your webhook signing secret is: whsec_test_XXXX stripe trigger payment_intent.succeeded ```

---

Still Broken? Check These Too

1. Multiple Webhook Endpoints (Staging vs. Production)

  • You may be using the staging secret against production events. Verify process.env.NODE_ENV and load corresponding secret from .env.production or .env.staging.
  • 2. Body Logged Before Middleware

  • If you're logging req.body in authentication middleware *before* the webhook route, it consumes the stream. Move logging *after* verification or use req.rawBody capture.
  • 3. Stripe Library Version Mismatch

  • Pre-v11 used stripe.webhooks.constructEvent(). v11+ uses stripe.webhooks.constructEvent() identically, but ensure your Node.js version matches Stripe SDK requirements. *We're uncertain if v14+ changed this API; check official docs if upgrading.*
  • ---

    Related Guides

  • [Express body-parser: choosing the right middleware](/?guide=express-middleware-breakdown)
  • [Environment variables: loading secrets safely in production](/?guide=dotenv-best-practices)
  • ---

    Official Resources

  • [Stripe Webhooks Documentation](https://stripe.com/docs/webhooks)
  • [Stripe Node.js SDK: Webhook Verification](https://stripe.com/docs/webhooks/signatures)
  • [Express.js Body Parser Middleware](https://expressjs.com/en/resources/middleware/body-parser.html)
  • ---

    Found a different variation? Drop it in the comments. 2am debugging is rough—help the next person by sharing your specific error and fix.

    🔥 0d
    LIVE
    ↓ PlanetScale rage spiking •⚠ Vercel pricing complaints •↑ Railway gaining fast •↑ Supabase happiness rising •↑ Resend loved by devs •↓ PlanetScale rage spiking •⚠ Vercel pricing complaints •↑ Railway gaining fast •↑ Supabase happiness rising
    DEVELOPER PAIN RADAR // Loading...

    Developers complain.
    Opportunities appear.

    We track what developers are struggling with today — and what opportunities that creates.

    —
    guides today
    —
    avg happiness
    🔥 Pain
    📖 Guides
    🔭 Explore
    👤 Mine
    🔥 Pain Radar — rage scores today
    ↗ share
    💡 Opportunity Feed — pain = market gap
    📈 Tool Momentum
    all scores →
    📖 Latest Guide
    all guides →
    📖 All Guides
    📊 Tool Scores
    + Submit
    📰 Hacker News
    ➕ Submit a Tool
    ← back