Skip to main content
General
Billing

Webhooks

Learn how to handle Stripe webhooks.

Open MarkdownFull AI corpusFeedback

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

  1. Go to your Stripe Dashboard
  2. Click "Add endpoint"
  3. Enter your webhook URL: https://yourdomain.com/api/webhooks/stripe
  4. Select the required events (see list below)
  5. 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

20 events selected
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.deleted

The following Stripe webhook events are handled by the webhook handler:

Subscription Events

  • customer.subscription.created - When a new subscription is created
  • customer.subscription.updated - When a subscription is modified (plan changes, status updates)
  • customer.subscription.deleted - When a subscription is canceled or expires
  • customer.subscription.trial_will_end - When a trial is ending soon (3 days before)
  • customer.subscription.paused - When a subscription is paused
  • customer.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 succeeds
  • invoice.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 initiated
  • refund.updated - When a refund's status updates
  • refund.failed - When a refund fails

Dispute Events

  • charge.dispute.created - When a customer disputes a charge
  • charge.dispute.updated - When a dispute status updates
  • charge.dispute.closed - When a dispute is resolved
  • charge.dispute.funds_withdrawn - Funds withdrawn from balance
  • charge.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 purchases
  • customer.subscription.created - Creates subscription records
  • customer.subscription.updated - Updates subscription status and plan changes
  • customer.subscription.deleted - Marks subscriptions as canceled
  • customer.subscription.trial_will_end - Sends trial ending notifications
  • customer.subscription.paused - Handles subscription pauses
  • customer.subscription.resumed - Handles subscription resumption
  • invoice.paid - Logs successful invoice payments
  • invoice.payment_failed - Sends payment failure notifications
  • charge.refunded - Handles refunds (full and partial)
  • refund.created - Tracks refund lifecycle
  • refund.updated - Updates refund status
  • refund.failed - Logs refund failure
  • charge.dispute.created - Alerts admins of new chargebacks
  • charge.dispute.updated - Updates dispute status
  • charge.dispute.closed - Logs dispute resolution
  • customer.deleted - Clears Stripe customer ID from organizations
  • payment_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:

app/api/webhooks/stripe/route.ts
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:

Terminal
npm run stripe:listen

The 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.

You can ask the Stripe CLI to send a fixture event through the listener:

Terminal
npm run stripe:trigger -- payment_intent.succeeded

A 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:

  1. Confirm the endpoint URL is the final HTTPS origin plus /api/webhooks/stripe.
  2. Confirm the endpoint is subscribed to every event used by your enabled billing modes.
  3. Complete a test-mode checkout and inspect its delivery in Stripe's webhook event log.
  4. Check that the application recorded the event and updated the intended organization.
  5. 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.