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