10 Stripe Webhook Best Practices for Production
Learn how to handle Stripe webhooks reliably in production. Best practices for verification, idempotency, error handling, and more.
Introduction
Stripe webhooks are the backbone of reliable payment integrations. They notify your application about events like successful payments, subscription renewals, and failed charges. Here are 10 best practices for handling Stripe webhooks in production.
1. Always Verify Webhook Signatures
Never trust incoming webhooks without verification. Stripe signs every webhook with your endpoint secret.
const sig = request.headers['stripe-signature'];
const event = stripe.webhooks.constructEvent(
request.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
Using Hookbase? Signature verification is automatic for all Stripe sources.
2. Return 200 Quickly
Stripe expects a response within 10 seconds. If your processing takes longer, return 200 immediately and process asynchronously.
// Bad: Processing before responding
app.post('/webhook', async (req, res) => {
await processPayment(req.body); // Might timeout!
res.json({ received: true });
});
// Good: Respond first, process later
app.post('/webhook', (req, res) => {
res.json({ received: true });
processPayment(req.body);
});
3. Handle Idempotency
Stripe may send the same webhook multiple times. Use the event ID to ensure you only process each event once.
const eventId = event.id;
if (await alreadyProcessed(eventId)) {
return res.json({ received: true });
}
await markAsProcessed(eventId);
// Process the event
4. Listen for the Right Events
Don't listen for more events than you need. Each event type has specific use cases:
payment_intent.succeeded- One-time payments completedinvoice.paid- Subscription payments completedcustomer.subscription.updated- Plan changescustomer.subscription.deleted- Cancellations
5. Use Test Mode for Development
Always test webhooks in Stripe's test mode first. Use test API keys and the Stripe CLI to send test events to your local server.
stripe listen --forward-to localhost:3000/webhook
Or use Hookbase tunnels to receive real test webhooks locally.
6. Log Everything
Log every webhook you receive, even if you don't process it. This helps debug issues later.
console.log({
eventId: event.id,
type: event.type,
timestamp: new Date().toISOString(),
processed: true
});
7. Handle Failures Gracefully
If processing fails, throw an error so Stripe retries the webhook.
try {
await processEvent(event);
res.json({ received: true });
} catch (error) {
console.error('Processing failed:', error);
res.status(500).json({ error: 'Processing failed' });
}
8. Set Up Alerts
Monitor your webhook endpoint health. Set up alerts for:
- High error rates
- Increased latency
- Queue backlog (if using async processing)
9. Use a Webhook Relay Service
Services like Hookbase add reliability layers:
- Automatic retries with exponential backoff
- Dead letter queue for failed deliveries
- Real-time monitoring and alerting
- Payload transformation for downstream services
10. Keep Secrets Secure
Never commit webhook secrets to version control. Use environment variables or a secrets manager.
# .env (never commit this file)
STRIPE_WEBHOOK_SECRET=whsec_xxx
Conclusion
Reliable Stripe webhook handling requires verification, idempotency, proper error handling, and good observability. Using a webhook relay service like Hookbase simplifies many of these challenges.
Ready to improve your Stripe webhook handling? Try Hookbase free.