Stripe Integration
Learn how to integrate Stripe webhooks with HooksDash for testing payment events locally.
Setup
1. Create a HooksDash Endpoint
# Via CLI hooksdash endpoints create --name "Stripe Development" --slug stripe-dev # Your webhook URL: https://hooksdash-api.ibadsiddiqui.dev/api/h/stripe-dev
2. Configure in Stripe
- Go to Stripe Dashboard
- Navigate to Developers → Webhooks
- Click Add endpoint
- Enter URL:
https://hooksdash-api.ibadsiddiqui.dev/api/h/stripe-dev - Select events to listen for
- Click Add endpoint
3. Get Your Webhook Secret
After creating the endpoint in Stripe, you'll see a Signing secret (starts with whsec_). Save this for signature verification.
Implementation
Basic Webhook Handler
const express = require('express'); const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY); const app = express(); // Stripe requires raw body for signature verification app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const sig = req.headers['stripe-signature']; const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; let event; try { event = stripe.webhooks.constructEvent(req.body, sig, webhookSecret); } catch (err) { console.error('Signature verification failed:', err.message); return res.status(400).send(`Webhook Error: ${err.message}`); } // Handle the event switch (event.type) { case 'payment_intent.succeeded': const paymentIntent = event.data.object; console.log('Payment succeeded:', paymentIntent.id); // Fulfill the order, send email, etc. break; case 'payment_intent.payment_failed': console.log('Payment failed:', event.data.object.id); // Notify customer, retry payment, etc. break; case 'customer.subscription.created': console.log('New subscription:', event.data.object.id); // Activate subscription, send welcome email break; case 'customer.subscription.deleted': console.log('Subscription cancelled:', event.data.object.id); // Deactivate features, send cancellation email break; default: console.log(`Unhandled event type ${event.type}`); } res.json({ received: true }); }); app.listen(3000);
TypeScript Implementation
import express from 'express'; import Stripe from 'stripe'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2023-10-16', }); const app = express(); app.post( '/webhook', express.raw({ type: 'application/json' }), async (req, res) => { const sig = req.headers['stripe-signature'] as string; let event: Stripe.Event; try { event = stripe.webhooks.constructEvent( req.body, sig, process.env.STRIPE_WEBHOOK_SECRET! ); } catch (err) { const error = err as Error; console.error('Signature verification failed:', error.message); return res.status(400).send(`Webhook Error: ${error.message}`); } // Handle the event switch (event.type) { case 'payment_intent.succeeded': { const paymentIntent = event.data.object as Stripe.PaymentIntent; await handlePaymentSuccess(paymentIntent); break; } case 'payment_intent.payment_failed': { const paymentIntent = event.data.object as Stripe.PaymentIntent; await handlePaymentFailure(paymentIntent); break; } default: console.log(`Unhandled event type ${event.type}`); } res.json({ received: true }); } ); async function handlePaymentSuccess(paymentIntent: Stripe.PaymentIntent) { console.log('Payment succeeded:', paymentIntent.id); // Your business logic here } async function handlePaymentFailure(paymentIntent: Stripe.PaymentIntent) { console.log('Payment failed:', paymentIntent.id); // Your business logic here } app.listen(3000);
Common Event Types
| Event | Description |
|---|---|
payment_intent.succeeded | Payment completed successfully |
payment_intent.payment_failed | Payment failed |
customer.created | New customer created |
customer.updated | Customer information updated |
customer.subscription.created | New subscription created |
customer.subscription.updated | Subscription updated |
customer.subscription.deleted | Subscription cancelled |
invoice.payment_succeeded | Invoice paid successfully |
invoice.payment_failed | Invoice payment failed |
Testing Locally
1. Start Your Server
node server.js
2. Start HooksDash Tunnel
hooksdash forward --port 3000 --endpoint stripe-dev
3. Send Test Webhook
In Stripe Dashboard:
- Go to Developers → Webhooks
- Click on your endpoint
- Click Send test webhook
- Select an event type
- Click Send test webhook
You should see the webhook arrive in your local server!
Production Deployment
When deploying to production:
- Create a separate HooksDash endpoint for production
- Update your Stripe webhook URL to point to your production server
- Use a different webhook secret for production
- Enable all relevant events in Stripe
- Implement proper error handling and retry logic
Best Practices
1. Verify Signatures
Always verify webhook signatures to ensure they come from Stripe:
stripe.webhooks.constructEvent(req.body, sig, webhookSecret);
2. Return 2xx Quickly
Respond with a 2xx status code within 5 seconds:
app.post('/webhook', async (req, res) => { // Verify signature const event = stripe.webhooks.constructEvent(req.body, sig, secret); // Respond immediately res.json({ received: true }); // Process asynchronously processStripeEvent(event).catch(console.error); });
3. Handle Duplicates
Stripe may send the same webhook multiple times. Implement idempotency:
const processedEvents = new Set(); app.post('/webhook', (req, res) => { const event = stripe.webhooks.constructEvent(req.body, sig, secret); if (processedEvents.has(event.id)) { return res.json({ received: true }); } processedEvents.add(event.id); // Process event... });
4. Use Webhook Logs
Check Stripe's webhook logs to debug delivery issues:
- Dashboard → Developers → Webhooks → [Your endpoint] → Recent events
Troubleshooting
Signature Verification Failed
Issue: Webhook Error: No signatures found matching the expected signature
Solution:
- Verify you're using
express.raw()middleware, notexpress.json() - Check that your webhook secret is correct
- Make sure you're passing the raw request body to
constructEvent
Webhook Not Received
Issue: Webhook sent from Stripe but not appearing in HooksDash
Solution:
- Verify the endpoint URL in Stripe matches your HooksDash endpoint
- Check that the endpoint is active in HooksDash
- Look at Stripe's webhook logs for delivery status
Local Forwarding Not Working
Issue: Webhook appears in HooksDash but not forwarded to localhost
Solution:
- Ensure the CLI tunnel is running:
hooksdash forward --port 3000 - Verify your local server is running
- Check that the port number is correct