How the pieces connect
The data flow is one-directional and server-authoritative. Your client (web or mobile) calls Stripe Checkout or the Billing Portal to start a payment, but the source of truth for entitlement is Postgres, not the client. When a payment, subscription, or invoice state changes, Stripe POSTs an event to a webhook handler running as a Supabase Edge Function (Deno). That function verifies the signature, reads event.type (checkout.session.completed, customer.subscription.updated, invoice.payment_failed, and similar), and upserts the relevant row into a subscriptions or customers table.
The link between the two systems is the stripe_customer_id. Create the Stripe Customer once, store its id against the Supabase auth.users id, and every later webhook can resolve back to the right user. The client never decides what tier someone is on; it just reads the row Postgres already holds.