Stripe Webhook
Handle Stripe webhook events.
Accepts all events from Stripe but only processes:
- invoice.paid → NDIS invoice grants (RevenueCat 12-month) + recurring Pay It Forward contributions (pool credit per renewal).
- invoice.finalized → flips a pending NDIS row to 'invoiced' (our "Sent"), so finalizing in the Stripe dashboard updates our status the same as the admin Send button. Mirrors adminPortal's mark_invoice_sent; no grants. Also the entry point for a Stripe revision: on a row-miss we re-point the original's row onto the replacement (see _repoint_revised_ndis_invoice).
- invoice.sent → parallel internal notification to the receivables watcher (Stripe does not support CC/BCC on invoice emails).
- invoice.voided → marks the ndis_invoice_requests row as 'cancelled', unless the invoice was voided into a revision (latest_revision set).
- invoice.marked_uncollectible → marks the row as 'uncollectible'.
- checkout.session.completed → one-off Pay It Forward gifts (sponsor-a-family) when mode='payment'; when mode='subscription', claims family_units.billing_contact_id for the parent who paid (#8920).
- customer.subscription.created / .updated → mirrors the live subscription onto family_units: stripe_product_id so the account UI can render the plan, subscription_cancel_at when a cancellation is booked for the end of the term, and stripe_subscription_id so a later .deleted can tell this subscription from one that replaced it. Skipped for a subscription that is not the live one. Writes no entitlement - a scheduled cancellation is not a status change.
- customer.subscription.deleted → the term has ended, so this writes the entitlement: subscription_status='canceled' and subscription_expires_at, plus nulling entitled_from_app_user_id, and clears all three mirror columns. Nothing else writes a status for a Stripe-direct family; RevenueCat never sees one. Skipped entirely when the event names a subscription the family has already replaced.
Anything carrying metadata.organisation_id is a CLINIC's event and is routed to clinic/billing.py before any of the above runs (tech plan §2.4): the clinic path writes organisation_billing and clinic_invoices and nothing a family reads, and the two must not braid. Handled there: checkout.session.completed, the subscription created / updated / deleted mirror, and the four invoice events, which record billed periods on the clinic_invoices ledger.
invoice.payment_failed is intentionally a no-op: Stripe leaves the invoice in 'open' status on a failed attempt and only attempt_count increments, so the canonical state hasn't changed. Keeping the row at 'invoiced' preserves the duplicate-submission gate in api/src/api/subscription/routes.py (status IN ('pending','invoiced')) so a parent cannot create a second Stripe invoice while the first is still open. Admins see attempt_count in the reconcile dialog. Future agents: do not "fix" this absence.
All other event types are acknowledged (200) but not processed.
Request
- Base URLhttps://api.projectindi.com (the document declares no server; this is the origin it was published from)
- Authnone declared
Response
Successful Response
Changes
No recorded changes to this operation across all 2 revisions of this API.