Webhooks

The Stripe events the app listens to, and how to test them locally.

Stripe tells the app when a payment or a subscription changes. The app listens at:

POST https://example.com/webhooks/stripe

It checks the Stripe-Signature header with the endpoint's signing secret (STRIPE_WEBHOOK_SECRET) and answers 400 when the signature does not match. It answers 200 for an event it handled or one it ignores, so Stripe stops sending it.

Events

EventWhat the app does
checkout.session.completedStarts the plan, or grants the credits of a pack once it is paid.
checkout.session.async_payment_succeededThe same, for a payment that cleared later.
customer.subscription.createdRecords the plan and its period.
customer.subscription.updatedRecords a change of plan, a cancellation or a renewal.
customer.subscription.deletedMoves the organization back to the free plan.
invoice.paidClears a past-due state once Stripe says the subscription is paid.
invoice.payment_failedStarts the grace period and shows the payment notice.
customer.tax_id.created, .updated, .deletedKeeps the customer's VAT number.
charge.refundedTakes back the refunded share of a credit pack's credits.
charge.dispute.createdTakes back the disputed credit pack's credits.

Create the endpoint in the Stripe dashboard with these events selected. An event delivered twice is handled once.

Test locally

Run the Worker locally (bun run preview in apps/web, port 8790), then forward Stripe's test events to it with the Stripe CLI:

stripe listen --forward-to localhost:8790/webhooks/stripe

The CLI prints a signing secret (whsec_...). Put it in STRIPE_WEBHOOK_SECRET of your local variables, then trigger an event:

stripe trigger checkout.session.completed

Use test mode only. Never point a test endpoint at live keys.