Stripe: webhook signature verification failing [2026 fix]

Webhook signature verification fails when endpoint secret is missing, misconfigured, or request body is altered. Use raw request body and correct endpoint secret from Dashboard.

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

TL;DR

Cause: Your code is using the wrong endpoint secret, or the request body is being parsed/modified before signature verification. Fix: Verify you're using the raw unparsed request body and the correct endpoint secret from Stripe Dashboard → Webhooks → Signing secret.

---

Exact Console Error Messages

Error 1: Missing Endpoint Secret

``` Error: Missing endpoint secret at Function.constructEvent (/node_modules/stripe/lib/webhooks.js:112:45) at POST /api/webhooks/stripe (webhook.js:8:12) ```

Error 2: Signature Mismatch

``` Error: No signatures found matching the expected signature for payload. Please check that the endpoint's signing secret is correct. at Function.verifySignature (/node_modules/stripe/lib/webhooks.js:156:89) ```

Error 3: Invalid Timestamp

``` Error: Timestamp outside the tolerance zone at Function.constructEvent (/node_modules/stripe/lib/webhooks.js:189:34) ```

Error 4: Malformed Webhook Header

``` StripeSignatureVerificationError: Unable to extract timestamp and signatures from header at verifySignature (/node_modules/stripe/lib/webhooks.js:78:11) ```

Error 5: Request Body Already Parsed

``` Error: No signatures found matching the expected signature for payload. Webhook payload must be passed as raw request body string, not parsed JSON ```

---

Broken Code → Fixed Code

❌ BROKEN: Using Parsed JSON Body

```javascript // Express.js - WRONG app.post('/api/webhooks/stripe', express.json(), (req, res) => { const sig = req.headers['stripe-signature']; const endpointSecret = 'whsec_test_secret'; try { // ❌ PROBLEM: req.body is already parsed JSON // Stripe signature is calculated on RAW bytes const event = stripe.webhooks.constructEvent( req.body, // This is an object, not a string! sig, endpointSecret ); } catch (err) { console.error('Webhook Error:', err.message); res.status(400).send(Webhook Error: ${err.message}); } }); ```

✅ FIXED: Using Raw Body Middleware

```javascript // Express.js - CORRECT app.post('/api/webhooks/stripe', // Use raw body BEFORE json() parser express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; // Fetch from environment variable, never hardcode const endpointSecret = process.env.STRIPE_ENDPOINT_SECRET; try { // ✅ CORRECT: req.body is raw Buffer/string const event = stripe.webhooks.constructEvent( req.body, // Raw bytes maintained sig, endpointSecret ); // Handle specific events switch (event.type) { case 'payment_intent.succeeded': // Process payment break; case 'charge.refunded': // Process refund break; } res.json({received: true}); } catch (err) { console.error('Webhook Error:', err.message); res.status(400).send(Webhook Error: ${err.message}); } } ); ```

❌ BROKEN: Wrong Endpoint Secret

```javascript // Using publishable key or API key instead of signing secret const endpointSecret = process.env.STRIPE_API_KEY; // ❌ WRONG ```

✅ FIXED: Correct Secret Source

```javascript // Stripe Dashboard → Developers → Webhooks → (click endpoint) → Signing secret const endpointSecret = process.env.STRIPE_ENDPOINT_SECRET; // ✅ CORRECT // Example format: whsec_live_51234567890abcdef or whsec_test_... ```

❌ BROKEN: Middleware Order Issue

```javascript app.use(express.json()); // Parses ALL incoming JSON

app.post('/api/webhooks/stripe', (req, res) => { // ❌ req.body is already parsed, can't verify signature const event = stripe.webhooks.constructEvent( req.body, sig, secret ); }); ```

✅ FIXED: Route-Specific Raw Body

```javascript // Webhook endpoint gets raw body app.post('/api/webhooks/stripe', express.raw({type: 'application/json'}), webhookHandler );

// Other endpoints get parsed JSON app.use(express.json()); app.post('/api/other', otherHandler); ```

---

Still Broken? Check These Too

1. Timestamp Tolerance: Stripe rejects webhooks older than 5 minutes. If your server clock is off by >5 min, verification fails. Run date and sync system time.

2. Multiple Endpoint Secrets: You might have multiple webhook endpoints in Stripe Dashboard. Verify you're using the secret for the endpoint that's actually receiving the request. Each endpoint has a unique secret.

3. Environment Variable Not Loading: Confirm process.env.STRIPE_ENDPOINT_SECRET is actually set. Add console.log('Secret:', process.env.STRIPE_ENDPOINT_SECRET?.substring(0, 10)) to verify it's loading (never log the full secret).

4. Reverse Proxy/Load Balancer Modifying Body: If behind nginx/AWS ALB, ensure raw body is passed through unchanged. Some proxies may gzip or re-encode, breaking the signature.

5. Testing with Wrong Mode Secret: Ensure your test webhook uses whsec_test_* secret, production webhooks use whsec_live_*. Using test secret to verify live webhooks will fail.

---

Version Note

This guide covers stripe npm package v13+. [Check your version](/?guide=stripe-version-check). Stripe.js webhook handling is consistent across versions, but middleware setup differs for Express v4 vs v5—explicitly test your Express major version if unsure.

---

Related Guides

  • [Stripe API Key Errors Explained](/?guide=stripe-auth-errors)
  • [Webhook Testing Locally with ngrok](/?guide=stripe-local-webhook-testing)
  • ---

    Official Reference

    📖 [Stripe Webhook Signature Verification](https://stripe.com/docs/webhooks/signatures)

    ---

    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