Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch caused by wrong endpoint secret or missing raw body parsing—use correct secret from Dashboard and parse raw request body.

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

TL;DR

Cause: You're using the wrong webhook endpoint secret or parsing the request body as JSON before signature verification. Fix: Grab the correct whsec_ secret from Stripe Dashboard → Developers → Webhooks, and verify the signature against the raw request body *before* parsing.

---

Real Console Error Messages

``` Error: No signatures found matching the expected signature for payload. at WebhookEndpoint.constructEvent (index.js:142:15) ```

``` StripeSignatureVerificationError: No signatures found matching the expected signature for payload. Webhook secret provided was: 'whsec_test_***' ```

``` throw new errors.StripeSignatureVerificationError( ^ StripeSignatureVerificationError: Timestamp outside the tolerance zone ```

``` Error: Invalid signature. Expected sig=sha256=abc123..., got sig=sha256=xyz789... ```

``` Failed to verify webhook signature: expected body to be a string or Buffer ```

---

The Problem: Broken vs Fixed Code

❌ BROKEN: Parsing JSON Before Signature Check

```javascript // Express.js - WRONG ❌ app.post('/webhook', express.json(), (req, res) => { const sig = req.headers['stripe-signature']; // ERROR: Body was already parsed to JSON object const event = stripe.webhooks.constructEvent( req.body, // ← THIS IS AN OBJECT NOW, NOT RAW STRING sig, process.env.STRIPE_WEBHOOK_SECRET ); res.json({received: true}); }); ```

✅ FIXED: Raw Body + Signature Verification

```javascript // Express.js - CORRECT ✅ app.post('/webhook', express.raw({type: 'application/json'}), // ← Raw buffer, not parsed (req, res) => { const sig = req.headers['stripe-signature']; let event; try { // Pass raw body string/buffer first event = stripe.webhooks.constructEvent( req.body, // ← NOW A BUFFER - CORRECT sig, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.log(Webhook Error: ${err.message}); return res.status(400).send(Webhook Error: ${err.message}); } // NOW safe to process event if (event.type === 'payment_intent.succeeded') { console.log('Payment succeeded!'); } res.json({received: true}); } ); ```

❌ BROKEN: Wrong Secret

```javascript // Using pk_live_ (public key) instead of whsec_ (webhook secret) const secret = process.env.STRIPE_PUBLIC_KEY; // ← WRONG const event = stripe.webhooks.constructEvent(req.body, sig, secret); ```

✅ FIXED: Correct Secret

```javascript // Get from Stripe Dashboard → Developers → Webhooks → Signing secret const secret = process.env.STRIPE_WEBHOOK_SECRET; // ← whsec_xxx const event = stripe.webhooks.constructEvent(req.body, sig, secret); ```

---

Step-by-Step Verification Checklist

1. Confirm the Secret

  • Go to [Stripe Dashboard](https://dashboard.stripe.com/webhooks)
  • Find your endpoint
  • Click "Reveal" next to the signing secret
  • Copy the whsec_test_ or whsec_live_ value
  • Paste into .env: STRIPE_WEBHOOK_SECRET=whsec_xxx
  • Restart your server
  • 2. Verify Raw Body Handling

    ```javascript // Express app.post('/webhook', express.raw({type: 'application/json'}), ...)

    // Fastify app.post('/webhook', async (request, reply) => { const sig = request.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( request.rawBody, // ← Fastify exposes raw body here sig, process.env.STRIPE_WEBHOOK_SECRET ); })

    // Next.js API Routes export const config = { api: { bodyParser: false, // ← Disable automatic parsing }, };

    export default async function handler(req, res) { const buf = await buffer(req); const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( buf, // ← Pass the buffer sig, process.env.STRIPE_WEBHOOK_SECRET ); } ```

    3. Check Environment Variable Loading

    ```bash

    Verify secret is actually loaded

    echo $STRIPE_WEBHOOK_SECRET

    Should output: whsec_test_51234567890...

    ```

    ---

    Still Broken? Check These Too

    1. Timestamp Tolerance (too old) – Stripe rejects webhooks older than 5 minutes by default. Check server time is synchronized. Adjust tolerance: stripe.webhooks.constructEvent(body, sig, secret, 600) for 10-minute window.

    2. Multiple Endpoints with Different Secrets – If you have test and live endpoints, ensure you're using the correct secret for the environment. Test webhooks use whsec_test_, production uses whsec_live_. [See webhook environment guide](/?guide=stripe-environments).

    3. Proxy/Load Balancer Modifying Body – Intermediate servers sometimes compress or modify the raw request. Disable compression for /webhook routes or use a direct endpoint. [See reverse proxy troubleshooting](/?guide=webhook-proxy-issues).

    ---

    Version Notes

    Behavior unchanged in stripe@11.x through stripe@14.x (2024-2026). Raw body handling is consistent across frameworks, but framework setup differs—verify your framework's documentation for disabling body parsing.

    ---

    Official Resources

  • [Stripe Webhook Signature Verification Docs](https://stripe.com/docs/webhooks/signatures)
  • [Stripe JavaScript Library Reference](https://github.com/stripe/stripe-node)
  • [Testing Webhooks Locally with Stripe CLI](https://stripe.com/docs/stripe-cli/webhooks-setup)
  • ---

    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