Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch: endpoint secret mismatch or missing raw body. Verify STRIPE_WEBHOOK_SECRET env var and pass raw request body to verifyHeader().

Stripe: Webhook Signature Verification Failing [2am Emergency Fix]

TL;DR

Cause: Your endpoint secret doesn't match what Stripe is using, or you're passing the parsed JSON body instead of the raw request body to signature verification. Fix: Verify STRIPE_WEBHOOK_SECRET matches your dashboard endpoint, and always pass req.rawBody (not req.body) to stripe.webhooks.constructEvent().

---

Exact Error Messages You'll See

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

``` StripeSignatureVerificationError: Unable to extract timestamp and signatures from header ```

``` Error: Webhook signature verification failed. Webhook signing secret does not match. ```

``` Signature verification failed. Invalid signature for request payload. ```

``` StripeSignatureVerificationError: No match found for signature ```

---

Broken Code → Fixed Code

Problem #1: Using Parsed Body Instead of Raw Body

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

app.post('/webhook', express.json(), (req, res) => { const sig = req.headers['stripe-signature']; try { // ❌ WRONG: req.body is already parsed JSON const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET ); // ... } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } }); ```

FIXED: ```javascript const express = require('express'); 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']; try { // ✅ CORRECT: req.body is Buffer/raw bytes const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET ); // ... } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } } ); ```

Key difference: Use express.raw({ type: 'application/json' }) NOT express.json().

Problem #2: Environment Variable Mismatch

BROKEN: ```javascript const endpointSecret = 'whsec_test123'; // ❌ Hardcoded

const event = stripe.webhooks.constructEvent( req.body, sig, endpointSecret ); ```

FIXED: ```javascript const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

if (!endpointSecret) { throw new Error('STRIPE_WEBHOOK_SECRET environment variable missing'); }

const event = stripe.webhooks.constructEvent( req.body, sig, endpointSecret ); ```

Verification steps: 1. Go to Stripe Dashboard → Developers → Webhooks 2. Find your endpoint, click it 3. Click "Reveal" next to Signing secret 4. Copy the whsec_... value 5. Set it as STRIPE_WEBHOOK_SECRET in your .env file 6. Restart your server 7. Verify with echo $STRIPE_WEBHOOK_SECRET in production

Problem #3: Middleware Order (Express)

BROKEN: ```javascript // ❌ JSON parsing happens first, consuming the raw body app.use(express.json());

app.post('/webhook', (req, res) => { const event = stripe.webhooks.constructEvent(req.body, sig, secret); }); ```

FIXED: ```javascript // ✅ Raw parsing for webhook BEFORE global JSON middleware app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const event = stripe.webhooks.constructEvent(req.body, sig, secret); } );

// Other routes get normal JSON parsing app.use(express.json());

app.post('/api/other', (req, res) => { // req.body is parsed here }); ```

---

Still Broken? Check These Too

1. Trailing/Leading Whitespace in Secret: Copy your webhook secret directly from the Dashboard. Paste into .env without quotes: STRIPE_WEBHOOK_SECRET=whsec_abc123... (not "whsec_abc123..."). Verify no whitespace with echo "${STRIPE_WEBHOOK_SECRET}" | od -c.

2. Timestamp Too Old: Stripe rejects signatures older than 5 minutes by default. If your server time is drifting, run ntpdate -s time.nist.gov or check system clock: date. [Related webhook testing guide](/?guide=stripe-webhook-testing).

3. Multiple Endpoints Configured: You may have configured this webhook in both test and live mode with different secrets. Verify you're using the correct secret for the mode (test vs. live). Dashboard shows both—make sure env vars match the mode you're testing.

4. Reverse Proxy Stripping Headers: If behind nginx/CloudFlare, ensure stripe-signature header isn't being stripped. Check with console.log(req.headers) in your handler.

5. Stripe SDK Version Mismatch: [Verify your stripe package version](/?guide=stripe-package-versions) matches your code. As of 2026, we're on stripe@17+, but older code might expect stripe@11 APIs.

---

Verification Checklist

  • [ ] Using express.raw() not express.json() for webhook route
  • [ ] STRIPE_WEBHOOK_SECRET copied exactly from Dashboard (test or live)
  • [ ] Environment variable loaded before webhook handler runs
  • [ ] Raw request body passed to constructEvent(), not parsed JSON
  • [ ] Server clock within 5 minutes of actual time
  • [ ] Webhook endpoint public/accessible from internet
  • [ ] No request body modification middleware before webhook handler
  • ---

    Official Resources

    [Stripe Webhook Signature Verification Docs](https://stripe.com/docs/webhooks/signatures)

    ---

    Found a different variation? Drop it in the comments.

    🔥 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