How to Connect Stripe Webhooks to Your Application
Stripe webhooks allow your application to react to payment events in real time — things like successful charges, subscription renewals, or failed payments. Instead of polling Stripe's API repeatedly, your server receives an HTTP POST notification the moment something happens. This guide walks you through setting up a secure, production-ready Stripe webhook integration.
How Stripe Webhooks Work
When an event occurs in your Stripe account (e.g., a payment succeeds), Stripe sends an HTTP POST request to a URL you've registered. The request body contains a JSON payload describing the event. Your job is to:
- Expose a public HTTPS endpoint on your server
- Verify the request came from Stripe (not a spoofed attacker)
- Parse the event and take appropriate action
- Respond with a
200 OKto acknowledge receipt
Step 1: Register Your Webhook Endpoint in Stripe Dashboard
Go to Stripe Dashboard → Developers → Webhooks and click Add endpoint. Enter your server URL (e.g., https://yourdomain.com/webhooks/stripe) and select the events you want to listen for. Common events include:
payment_intent.succeededpayment_intent.payment_failedcustomer.subscription.createdinvoice.payment_failed
Once registered, Stripe will show you a Webhook Signing Secret — copy it and store it as an environment variable. Never hardcode it in your source.
Step 2: Create the Webhook Endpoint
Here's a minimal example in Node.js using Express:
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.post('/webhooks/stripe',
express.raw({ type: 'application/json' }),
(req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'payment_intent.succeeded':
handlePaymentSuccess(event.data.object);
break;
// handle other events
}
res.json({ received: true });
}
);
Important: Use express.raw() (not express.json()) for the webhook route. Stripe's signature verification requires the raw, unparsed request body.
Step 3: Verify the Webhook Signature
The stripe.webhooks.constructEvent() call computes an HMAC-SHA256 hash of the request body using your signing secret and compares it to the stripe-signature header. If they don't match, it throws an error — reject the request immediately. This protects you against replay attacks and spoofed payloads.
Step 4: Handle Events Idempotently
Stripe may deliver the same event more than once. Your handler must be idempotent — processing the same event twice should not cause duplicate actions. A common pattern is to store the event.id in your database and skip processing if you've already seen it.
Testing Locally with the Stripe CLI
You don't need a public server to test webhooks during development. Install the Stripe CLI and run:
stripe listen --forward-to localhost:3000/webhooks/stripe
This tunnels webhook events to your local server and also lets you trigger test events with stripe trigger payment_intent.succeeded.
Production Checklist
- ✅ Always use HTTPS for your webhook endpoint
- ✅ Verify the Stripe signature on every request
- ✅ Implement idempotency using
event.id - ✅ Return
200 OKquickly; process events asynchronously if needed - ✅ Log all events for debugging and auditing