Stripe: webhook signature verification failing [2026 fix]

Webhook signature mismatch: endpoint secret misconfigured or request body modified before verification. Use raw body buffer, never parsed JSON.

Stripe: Webhook Signature Verification Failing [2026 Fix]

TL;DR

Cause: Your endpoint is verifying the signature against parsed JSON instead of the raw request body, or your webhook endpoint secret is mismatched between Stripe dashboard and your code.

Fix: Pass the raw unparsed request body to stripe.webhooks.constructEvent() and verify your endpoint secret matches exactly in your .env file.

---

Real Console Error Messages

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

``` Stripe.error.SignatureVerificationError: No signatures found matching the expected signature for payload. Webhook signing secret: whsec_... ```

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

``` SignatureVerificationError: No signatures found matching the expected signature for payload. [402] Request body does not match signature ```

``` Error: Invalid request: missing or invalid stripe-signature header ```

---

Broken Code vs. Exact Fix

❌ BROKEN (Express.js)

```javascript const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/webhook', express.json(), (req, res) => { const sig = req.headers['stripe-signature']; const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET; try { // WRONG: Body is already parsed JSON const event = stripe.webhooks.constructEvent( req.body, // ← PROBLEM: Should be raw buffer sig, endpointSecret ); res.json({received: true}); } catch (err) { res.status(400).send(Webhook Error: ${err.message}); } }); ```

✅ FIXED (Express.js)

```javascript const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

// Use raw body parser ONLY for webhook endpoint app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => { const sig = req.headers['stripe-signature']; const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET; // Verify endpointSecret exists if (!endpointSecret) { return res.status(400).send('Webhook secret not configured'); } try { // CORRECT: Pass raw buffer const event = stripe.webhooks.constructEvent( req.body, // ← Raw Buffer object sig, endpointSecret ); // Handle specific events switch(event.type) { case 'payment_intent.succeeded': console.log('Payment succeeded:', event.data.object.id); break; case 'charge.failed': console.log('Charge failed:', event.data.object.id); break; } res.json({received: true}); } catch (err) { console.error('Webhook signature verification failed:', err.message); res.status(400).send(Webhook Error: ${err.message}); } }); ```

Common Framework-Specific Setups

Next.js API Routes: ```javascript import { buffer } from 'micro';

export const config = { api: { bodyParser: false } };

export default async (req, res) => { if (req.method === 'POST') { const buf = await buffer(req); const sig = req.headers['stripe-signature']; const event = stripe.webhooks.constructEvent( buf, sig, process.env.STRIPE_WEBHOOK_SECRET ); // Process event... } }; ```

Django: ```python from django.views.decorators.csrf import csrf_exempt from django.http import JsonResponse import stripe

@csrf_exempt def stripe_webhook(request): payload = request.body # Raw bytes sig_header = request.META.get('HTTP_STRIPE_SIGNATURE') endpoint_secret = os.environ['STRIPE_WEBHOOK_SECRET'] try: event = stripe.Webhook.construct_event( payload, sig_header, endpoint_secret ) except ValueError: return JsonResponse({'error': 'Invalid payload'}, status=400) ```

---

Verification Checklist

1. Endpoint Secret Match - Go to Stripe Dashboard → Developers → Webhooks - Copy exact whsec_... value - Paste into your .env file: STRIPE_WEBHOOK_SECRET=whsec_live_... - Restart your server after .env change

2. Raw Body Requirement - Signature verification depends on byte-for-byte matching - Any JSON parsing before verification breaks the signature - Use express.raw() or framework equivalent

3. Middleware Order - Webhook route must come BEFORE global express.json() middleware ```javascript app.post('/webhook', express.raw({type: 'application/json'}), handler); app.use(express.json()); // Other routes ```

---

Still Broken? Check These Too

1. [Stripe API Key Format](/guide=stripe-key-validation) — Verify you're using sk_live_ (production) or sk_test_ (test mode), not publishable key

2. Webhook Endpoint Not Registered — In Stripe Dashboard, confirm webhook URL is exactly https://yourdomain.com/webhook (no trailing slash difference)

3. [Request Body Logging Issues](/guide=stripe-webhook-debugging) — Never log req.body after parsing; log the raw signature header instead

4. Timestamp Tolerance — Stripe webhooks fail if server time is >5 minutes out of sync; check system clock

5. Load Balancer/Proxy Modifying Body — Some proxies recompress or modify the request body; verify raw bytes match what Stripe sent

---

Official Documentation

[Stripe Webhook Signature Verification Docs](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