Webhooks
Learn how to handle Stripe webhooks.
Webhooks are used to receive events from Stripe. They are important to get the latest data so your application is in sync with Stripe.
Setting up Webhooks
- Go to your Stripe Dashboard
- Click "Add endpoint"
- Enter your webhook URL:
https://yourdomain.com/api/webhooks/stripe - Select the required events (see list below)
- Copy the webhook signing secret
Required Webhook Events
Choose Stripe webhook events
Keep the groups used by your billing configuration, then copy the exact event list into Stripe Dashboard.
8 events
2 events
9 events
1 event
customer.subscription.created
customer.subscription.updated
customer.subscription.deleted
customer.subscription.trial_will_end
customer.subscription.paused
customer.subscription.resumed
invoice.paid
invoice.payment_failed
checkout.session.completed
payment_intent.succeeded
charge.refunded
refund.created
refund.updated
refund.failed
charge.dispute.created
charge.dispute.updated
charge.dispute.closed
charge.dispute.funds_withdrawn
charge.dispute.funds_reinstated
customer.deletedThe following Stripe webhook events are handled by the webhook handler:
Subscription Events
customer.subscription.created- When a new subscription is createdcustomer.subscription.updated- When a subscription is modified (plan changes, status updates)customer.subscription.deleted- When a subscription is canceled or expirescustomer.subscription.trial_will_end- When a trial is ending soon (3 days before)customer.subscription.paused- When a subscription is pausedcustomer.subscription.resumed- When a paused subscription is resumed
Checkout Events
checkout.session.completed- When a checkout session completes (subscriptions, one-time payments, credit purchases)
Invoice Events
invoice.paid- When an invoice payment succeedsinvoice.payment_failed- When an invoice payment fails
Charge Events
charge.refunded- When a charge is refunded (handles both full and partial refunds)
Refund Events
refund.created- When a refund is initiatedrefund.updated- When a refund's status updatesrefund.failed- When a refund fails
Dispute Events
charge.dispute.created- When a customer disputes a chargecharge.dispute.updated- When a dispute status updatescharge.dispute.closed- When a dispute is resolvedcharge.dispute.funds_withdrawn- Funds withdrawn from balancecharge.dispute.funds_reinstated- Funds reinstated to balance
Customer Events
customer.deleted- When a customer is deleted from Stripe
Payment Intent Events
payment_intent.succeeded- When a payment intent succeeds (for audit logging)
Webhook Handler
The starter kit includes a comprehensive webhook handler at app/api/webhooks/stripe/route.ts that handles all billing events. The handler includes:
- Signature verification - Validates webhook authenticity using Stripe's signature
- Idempotency - Prevents duplicate processing of the same event
- Error handling - Distinguishes between transient and permanent errors
- Event logging - Records all events in the database for audit trails
Supported Events
The handler processes the following events:
checkout.session.completed- Handles subscriptions, one-time payments and credit purchasescustomer.subscription.created- Creates subscription recordscustomer.subscription.updated- Updates subscription status and plan changescustomer.subscription.deleted- Marks subscriptions as canceledcustomer.subscription.trial_will_end- Sends trial ending notificationscustomer.subscription.paused- Handles subscription pausescustomer.subscription.resumed- Handles subscription resumptioninvoice.paid- Logs successful invoice paymentsinvoice.payment_failed- Sends payment failure notificationscharge.refunded- Handles refunds (full and partial)refund.created- Tracks refund lifecyclerefund.updated- Updates refund statusrefund.failed- Logs refund failurecharge.dispute.created- Alerts admins of new chargebackscharge.dispute.updated- Updates dispute statuscharge.dispute.closed- Logs dispute resolutioncustomer.deleted- Clears Stripe customer ID from organizationspayment_intent.succeeded- Logs payment intents for audit
Extending the Handler
To add custom logic for a specific event, you can modify the handler functions in app/api/webhooks/stripe/route.ts. For example, to add custom logic when a subscription is created:
async function handleSubscriptionCreated(
eventId: string,
subscription: Stripe.Subscription
): Promise<void> {
// ... existing code ...
// Add your custom logic here
await sendWelcomeEmail(organizationId);
await createInitialResources(organizationId);
}Testing Webhooks
Install and authenticate the Stripe CLI, start the application and run the included listener from a second terminal:
npm run stripe:listenThe CLI prints a temporary whsec_... signing secret. Put that value in the
local .env as STRIPE_WEBHOOK_SECRET, then restart the development server so
the handler reads it.
Use the secret from the active listener
The Stripe CLI listener secret and the production endpoint secret are
different. Use the value printed by stripe listen locally. Store the
endpoint's Dashboard secret in the hosting provider for production.
You can ask the Stripe CLI to send a fixture event through the listener:
npm run stripe:trigger -- payment_intent.succeededA generated fixture proves that forwarding and signature verification work. It may not contain the organization, price and checkout metadata created by the application. Test state synchronization by completing a checkout through the local UI with Stripe test-mode credentials, then confirm the related order, subscription or credits in the application.
Verify Production Delivery
After deploying:
- Confirm the endpoint URL is the final HTTPS origin plus
/api/webhooks/stripe. - Confirm the endpoint is subscribed to every event used by your enabled billing modes.
- Complete a test-mode checkout and inspect its delivery in Stripe's webhook event log.
- Check that the application recorded the event and updated the intended organization.
- Resend the same event from Stripe and confirm it is treated as already processed rather than applying credits or access twice.
The handler returns a failure status for transient processing errors so Stripe can retry. Permanent data errors are recorded and acknowledged to avoid an endless retry loop. Monitor failed billing-event records and Stripe delivery attempts together when diagnosing synchronization problems.