Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch caused by wrong endpoint secret or missing raw request body in verification. Use correct secret from Dashboard and pass unparsed body to verify().

Stripe: Webhook Signature Verification Failing [2026 Fix]

TL;DR

Cause: You're verifying with the wrong endpoint secret OR passing JSON-parsed body instead of raw request buffer to stripe.webhooks.constructEvent().

Fix: Use the live/test secret matching your webhook URL from Stripe Dashboard, and always pass req.rawBody (bytes) not req.body (parsed JSON).

---

Exact Console Errors

Here are real error messages you're seeing at 2am:

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

``` Stripe signature verification failed: Webhook signature does not match the one provided. Webhook id: whsec_... ```

``` Fatal error in action POST /webhooks/stripe: Error: Webhook signature does not match the one provided. Webhook id: whsec_1O8x7pXxxx. Received signature does not match any of the configured signing secrets ```

``` ReferenceError: req.rawBody is undefined ```

``` SyntaxError: Cannot read property 'type' of undefined at Object.<anonymous> (webhooks.js:42:15) ```

---

Broken Code → Fixed Side-by-Side

❌ BROKEN: Using parsed body

```javascript app.post('/webhooks/stripe', (req, res) => { const sig = req.headers['stripe-signature']; try { // WRONG: req.body is already parsed JSON const event = stripe.webhooks.constructEvent( req.body, // ← PROBLEM: This is a JavaScript object, not raw bytes sig, process.env.STRIPE_ENDPOINT_SECRET ); console.log('Webhook received:', event.type); res.json({received: true}); } catch (err) { console.error('Webhook Error:', err.message); res.status(400).send(Webhook Error: ${err.message}); } }); ```

✅ FIXED: Using raw body buffer

```javascript app.post('/webhooks/stripe', (req, res) => { const sig = req.headers['stripe-signature']; try { // CORRECT: req.rawBody is the unparsed request buffer const event = stripe.webhooks.constructEvent( req.rawBody, // ← FIX: Raw request body as Buffer/string sig, process.env.STRIPE_ENDPOINT_SECRET ); console.log('Webhook received:', event.type); res.json({received: true}); } catch (err) { console.error('Webhook Error:', err.message); res.status(400).send(Webhook Error: ${err.message}); } }); ```

❌ BROKEN: Express middleware ordering

```javascript app.use(express.json()); // Parses body FIRST app.post('/webhooks/stripe', handleStripeWebhook); // Can't access raw body ```

✅ FIXED: Raw body before JSON parsing

```javascript // Stripe webhooks must come BEFORE JSON parser app.post('/webhooks/stripe', express.raw({type: 'application/json'}), handleStripeWebhook); app.use(express.json()); // Parse body for other routes ```

❌ BROKEN: Wrong endpoint secret

```javascript // Using signing secret instead of endpoint secret const secret = process.env.STRIPE_API_KEY; // ← WRONG const event = stripe.webhooks.constructEvent(body, sig, secret); ```

✅ FIXED: Correct endpoint secret from Dashboard

```javascript // Use whsec_* secret from Webhooks page, NOT your API key const secret = process.env.STRIPE_WEBHOOK_SECRET; // whsec_xxx const event = stripe.webhooks.constructEvent(body, sig, secret); ```

---

Step-by-Step Verification

1. Check Dashboard secret: Go to stripe.com → Developers → Webhooks → click your endpoint → copy "Signing secret" (starts with whsec_) 2. Verify environment variable: echo $STRIPE_WEBHOOK_SECRET should output whsec_xxxxx, NOT your API key 3. Test locally: Use Stripe CLI: stripe listen --forward-to localhost:3000/webhooks/stripe 4. Trigger test event: stripe trigger payment_intent.succeeded in another terminal 5. Check logs: You should see "Webhook received: payment_intent.succeeded"

I'm uncertain about version-specific differences in older versions of @stripe/stripe-js (pre-2022); those may require JSON.stringify(req.rawBody) instead of passing raw Buffer directly. Test this in your environment.

---

Still Broken? Check These Too

1. [Stripe webhook endpoint not triggering at all?](/?guide=stripe-webhook-not-called) — Verify URL is publicly accessible, HTTPS, and matches Webhook settings exactly 2. [HMAC signature algorithm mismatch](/?guide=stripe-webhook-hmac) — Some frameworks automatically base64-encode; ensure you're comparing raw bytes 3. [Webhook secret rotated but old code running](/?guide=stripe-secret-rotation) — Check if multiple app instances exist; restart all processes after env var update

---

Official Resources

  • [Stripe Webhook Signature Verification Docs](https://stripe.com/docs/webhooks/signatures)
  • [Stripe Webhook Testing Guide](https://stripe.com/docs/webhooks/test)
  • [Stripe CLI Webhook Forwarding](https://stripe.com/docs/stripe-cli/webhook-forwarding)
  • ---

    Found a different variation? Drop it in the comments — If you hit a different signature verification error (e.g., with Fastify, Django, or Node v18+), share it and we'll add it to this guide.

    🔥 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