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