Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch—verify endpoint secret format and raw request body handling in your webhook handler.

Stripe: Webhook Signature Verification Failing [2AM Fix]

TL;DR

Cause: Your webhook endpoint is using the Dashboard API key instead of the endpoint-specific signing secret, or you're parsing the request body before signature verification.

Fix: Use whsec_ or whsec_test_ endpoint secret from Webhook settings, verify the raw body before any JSON parsing, and ensure your framework isn't auto-parsing the body stream.

---

Real Console Error Messages

You'll likely see one of these exact errors:

``` Error: No signatures found matching the expected signature for payload. Discard webhook and investigate further if necessary. ```

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

``` Signature verification failed. Webhook signature does not match. ```

``` Error: Invalid signature. Expected sig, but got undefined ```

``` WebhookEndpointNotFound: Could not find webhook endpoint secret for endpoint_id=we_1234567890 ```

---

Broken Code vs. Exact Fix

Problem 1: Using Wrong Secret Type

Broken ❌ ```javascript const stripe = require('stripe')(process.env.STRIPE_API_KEY);

app.post('/webhook', express.json(), (req, res) => { const sig = req.headers['stripe-signature']; // WRONG: Using API key instead of endpoint secret const event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_API_KEY // ← THIS IS WRONG ); res.json({received: true}); }); ```

Fixed ✓ ```javascript const stripe = require('stripe')(process.env.STRIPE_API_KEY);

app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; // CORRECT: Using endpoint-specific signing secret const event = stripe.webhooks.constructEvent( req.body, // Must be raw Buffer, not parsed JSON sig, process.env.STRIPE_WEBHOOK_SECRET // whsec_test_... or whsec_... ); res.json({received: true}); }); ```

Problem 2: Body Parsed Before Verification

Broken ❌ ```javascript app.use(express.json()); // ← Parses body globally

app.post('/webhook', (req, res) => { const sig = req.headers['stripe-signature']; try { // req.body is already a JavaScript object, not raw bytes const event = stripe.webhooks.constructEvent( req.body, // ← WRONG: Already parsed sig, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { return res.status(400).send(Webhook Error: ${err.message}); } }); ```

Fixed ✓ ```javascript // Apply express.json() AFTER webhook route app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; try { // req.body is a Buffer—signature verification works const event = stripe.webhooks.constructEvent( req.body, // ← CORRECT: Raw Buffer sig, process.env.STRIPE_WEBHOOK_SECRET ); // Now handle event.type switch(event.type) { case 'payment_intent.succeeded': console.log('Payment succeeded:', event.data.object.id); break; } } catch (err) { return res.status(400).send(Webhook Error: ${err.message}); } res.json({received: true}); });

app.use(express.json()); // Apply AFTER webhook route ```

Problem 3: Environment Variable Not Set

Broken ❌ ```javascript // .env file missing or has wrong value STRIPE_WEBHOOK_SECRET=sk_test_1234... // ← Wrong prefix ```

Fixed ✓ ```bash

.env file must have endpoint secret, not API key

STRIPE_API_KEY=sk_test_... STRIPE_WEBHOOK_SECRET=whsec_test_...

Verify in Stripe Dashboard:

Developers → Webhooks → [Your Endpoint] → Signing Secret

```

---

Still Broken? Check These Too

1. Timestamp Outside 5-Minute Window - Stripe rejects signatures older than 5 minutes by default - Check server time sync: date (should match NTP) - Stripe uses t= in the signature header—if your server clock is off, verification fails - See [webhook timestamp validation guide](/?guide=stripe-timestamp-window)

2. Webhook Endpoint URL Not Matching - Stripe Dashboard shows https://example.com/webhook - Your code is actually at https://example.com/webhooks or https://api.example.com/webhook - Verify in Stripe Dashboard → Developers → Webhooks → edit endpoint URL - See [webhook routing issues](/?guide=webhook-endpoint-mismatch)

3. Firewall/Proxy Modifying Request Body - API gateway, load balancer, or reverse proxy adding headers/encoding - Signature is computed on original body—any modification breaks verification - Solution: Disable body transformation in middleware (disable gzip, content-type rewrites) - Log req.rawBody and compare to signature calculation

---

Quick Verification Checklist

  • [ ] Using express.raw({type: 'application/json'}) NOT express.json()
  • [ ] Environment variable STRIPE_WEBHOOK_SECRET starts with whsec_
  • [ ] Server time is synced (date output within ±5 seconds of NTP)
  • [ ] Webhook endpoint URL in Stripe Dashboard matches your route exactly
  • [ ] No middleware is parsing req.body before signature verification
  • [ ] Using latest stripe npm package (≥9.0.0 as of 2026)
  • ---

    Official Resources

    [Stripe Webhook Documentation](https://stripe.com/docs/webhooks) [Stripe Node.js SDK - webhooks.constructEvent](https://stripe.com/docs/api/node#construct_webhook_event)

    ---

    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