tools.queue config) to Postgres and Redis, including one-off purchase finalization and queue ack/retry behavior.
How it works
Stripe sends signed events to the backend; valid handled events are enqueued and processed once perevent.id using ProcessedWebhooksEvent plus a short-lived Redis key. Handlers receive validated payload shapes (see webhookValidators.ts), not raw Stripe types.
Event-level vs business-level idempotency
One-off checkout matrix (high level)
Invoice document lifecycle ownership
Invoice table lifecycle sync is owned by Stripe invoice lifecycle events:
One-off receipts are still owned by
payment_intent.succeeded and processor confirmPayment finalize logic, not by invoice.created / invoice.updated.
Recurring / subscription upgrades
For non–one-off checkout,payment_intent.succeeded may run before the invoice shape is complete; the handler can return true early when invoice is missing and rely on later events.
Checkout validation compares Stripe subscription line items to resolved Stripe price ids from price.ts (getUsageSubscriptionPriceIds, getStripePriceIdForProduct) — not Product.external_id. Successful upgrades hydrate subscriptions.price_id from the checked-out product’s active default price row.
Flow ownership is explicit:
Queue semantics: return value and retries
Processing runs instripeWebhookConsumer (apps/backend/src/tools/queue/consumers/stripeWebhookConsumer/index.ts):
Payload validation failures: handler not run; cache key removed; transaction rolled back.
Extending this system
- Add the event to
HandledStripeWebhookEventand the HTTP allowlist inapps/backend/src/routers/api/stripe.ts. - Add payload type in
webhookTypes.tsand Zod schema inwebhookValidators.ts. - Implement
handler(payload, eventId, transaction, tools)returningboolean. - Register in
index.tsgetHandlerswitch. - Add
__tests__spec and a row in the scenario matrix below when behavior is non-obvious. - Run
make test module=backend.
What not to do
- ❌ Bypass signature verification — always
constructEventwith raw body andwebhookSigningSecret. - ❌ Add high-cardinality tags to metrics/logs for Stripe ids in tags — use log fields where needed (see
.cursor/rules/security.mdc). - ❌ Depend on invoice preview Redis TTL alone for freshness — invalidation is explicit; TTL is a long backstop (
organizationInvoicePreview.ts).
File conventions
What’s next?
- Stripe — processor operations and setup.
- Billing architecture — broader billing context.
