Learn how to fix Stripe webhook signature errors by verifying secrets, handling raw payloads, solving body parsing issues, and debugging with Stripe CLI.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
How to Fix Stripe Webhook Signature Error
Step 1: Understand the Stripe Webhook Signature
Stripe adds a signature to webhook events to ensure they were sent by Stripe and not by a malicious third party. When you receive a "signature verification failed" error, it means that the signature in the webhook request doesn't match what Stripe expects.
Common causes of this error include:
Step 2: Verify Your Webhook Secret
Ensure you're using the correct webhook secret for the specific webhook endpoint.
Make sure this secret matches what you're using in your code:
// Node.js example
const endpointSecret = 'whsec\_...'; // This should match your Stripe Dashboard
Step 3: Properly Handle the Raw Webhook Body
A common mistake is modifying the payload before verifying the signature. You must use the raw body string for verification.
For Express.js with Node.js:
const express = require('express');
const stripe = require('stripe')('sk_test_...');
const app = express();
// Important: Use raw body for Stripe signature verification
app.use('/webhook', express.raw({type: 'application/json'}));
app.post('/webhook', (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
// Verify using the raw body and signature
event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
} catch (err) {
console.log(`⚠️ Webhook signature verification failed: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
console.log('✅ Success:', event.id);
res.json({received: true});
});
app.listen(3000, () => console.log('Running on port 3000'));
Step 4: Solve Body Parsing Issues
If you're using a framework that automatically parses JSON bodies, you need to ensure the raw body is preserved.
For Express.js (with middleware order):
const express = require('express');
const app = express();
// IMPORTANT: Route-specific middleware for Stripe webhooks
// must come BEFORE your general body parsers
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
// Webhook handling code
});
// General body parsing middleware for other routes
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Other routes...
Step 5: Handle the Payload in Different Frameworks
For Django:
# views.py
import stripe
from django.http import HttpResponse
from django.views.decorators.csrf import csrf\_exempt
@csrf\_exempt
def stripe\_webhook(request):
payload = request.body
sig_header = request.META['HTTP_STRIPE\_SIGNATURE']
endpoint_secret = 'whsec_...' # Your webhook secret
try:
event = stripe.Webhook.construct\_event(
payload, sig_header, endpoint_secret
)
except ValueError as e:
# Invalid payload
return HttpResponse(status=400)
except stripe.error.SignatureVerificationError as e:
# Invalid signature
return HttpResponse(status=400)
# Handle the event
return HttpResponse(status=200)
For Ruby on Rails:
# webhooks\_controller.rb
class WebhooksController < ApplicationController
skip_before_action :verify_authenticity_token
def create
payload = request.body.read
sig_header = request.env['HTTP_STRIPE\_SIGNATURE']
endpoint_secret = 'whsec_...'
begin
event = Stripe::Webhook.construct\_event(
payload, sig_header, endpoint_secret
)
rescue JSON::ParserError => e
# Invalid payload
render json: {error: e.message}, status: 400
return
rescue Stripe::SignatureVerificationError => e
# Invalid signature
render json: {error: e.message}, status: 400
return
end
# Handle the event
render json: {status: 'success'}
end
end
Step 6: Check for Timing Issues
Stripe has a tolerance window for signature verification (usually 5 minutes). If your server's clock is significantly off, or if there's a long delay in processing, this can cause signature verification to fail.
// Node.js example with custom tolerance
const event = stripe.webhooks.constructEvent(
payload,
signature,
endpointSecret,
10 \* 60 // 10 minute tolerance window in seconds
);
Step 7: Debug Using Stripe CLI
The Stripe CLI is an excellent tool for debugging webhook issues:
stripe login
stripe listen --forward-to http://localhost:3000/webhook
stripe trigger payment\_intent.succeeded
Step 8: Inspect Request Headers and Logs
Add detailed logging to capture all incoming webhook information:
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
console.log('Received webhook:');
console.log('Headers:', JSON.stringify(req.headers));
console.log('Body length:', req.body.length);
console.log('Stripe-Signature:', req.headers['stripe-signature']);
// Continue with signature verification...
});
Step 9: Properly Configure Reverse Proxies
If you're running behind a proxy (like Nginx, Apache, Cloudflare), it might modify the request body before it reaches your application.
For Nginx, ensure it correctly passes the raw body:
# nginx.conf
location /webhook {
proxy_pass http://your_backend\_server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote\_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Important for webhook body integrity
proxy_http_version 1.1;
proxy_request_buffering off;
}
Step 10: Use Testing Mode for Webhook Debugging
Stripe provides a test mode to help debug issues:
This allows you to debug without affecting your production environment.
Conclusion
By systematically following these steps, you should be able to identify and fix most Stripe webhook signature errors. Remember that maintaining the raw, unmodified request body is crucial for signature verification. If you continue experiencing issues, Stripe's support team can provide additional assistance by examining your specific webhook logs.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.