Keep Stripe customers, products, prices, subscriptions, invoices and payments in the stripe schema of your database, synced by webhooks.
Stripe sync copies your Stripe data into a stripe schema in your project's database and keeps it current. You can then join payments and subscriptions with your own tables in SQL and use them in row level security policies. Stripe sync is included from the Pro plan at no extra charge. The sync runs on your project's own server.
#Set it up
- Open Studio › Integrations › Stripe and add a Stripe secret key. A restricted key with read access is enough (Stripe › Developers › API keys › Create restricted key). Saving the key copies everything once.
- In Stripe › Developers › Webhooks, add an endpoint with the URL shown in Studio (
https://<project-ref>.p.stitchdb.com/stripe/v1/webhook) and select the events listed there. Then paste the endpoint's signing secret (whsec_…) in Studio.
Test-mode and live-mode keys both work; use the endpoint and signing secret from the same mode. When you roll the signing secret in Stripe, the previous one keeps working for 24 hours.
#Tables
| Table | Typed columns (the full object is in attrs) |
|---|---|
stripe.customers | email, name, phone, metadata, user_id (from metadata.user_id), deleted |
stripe.products | name, description, active, metadata |
stripe.prices | product, active, currency, unit_amount, type, recurring_interval, lookup_key |
stripe.subscriptions | customer, status, price (first item), current_period_end, cancel_at_period_end, trial_end, metadata, user_id |
stripe.subscription_items | subscription, price, quantity |
stripe.invoices | customer, subscription, status, currency, amount_due, amount_paid, hosted_invoice_url, period_end |
stripe.payment_intents | customer, status, amount, currency, metadata |
stripe.charges | customer, payment_intent, status, amount, amount_refunded, refunded |
stripe.refunds | charge, payment_intent, status, amount, currency |
stripe.checkout_sessions | customer, status, payment_status, mode, subscription, client_reference_id, amount_total |
Every table also has id, created, attrs (the Stripe object as JSON), updated_at and deleted. Deleted customers, products and prices stay, with deleted = true. Amounts are in the currency's smallest unit, as in Stripe. Events are applied once each, and an older event never overwrites newer data. The tables are read-only: anything you change in them is overwritten by the next event.
#Who can read it
Only the service_role can read the stripe schema, and the REST API does not expose it. To link Stripe customers to your users, create the customer with metadata: { user_id: '<auth user id>' } (or set it on the subscription). Then use the helper in your policies:
-- Only subscribers can read premium posts
create policy "subscribers read premium posts" on public.posts
for select to authenticated
using (not premium or stripe.has_active_subscription(auth.uid()));
-- A specific price (or one of the subscription's items)
select stripe.has_active_subscription(auth.uid(), 'price_123');stripe.has_active_subscription(user_id, price) is true when the user has a subscription that is active or trialing.
#Sync again
Sync all again in Studio copies every object again; use it after the sync was off for a while. Studio shows how many rows each table has, the last webhook received and any error from Stripe. Turn off stops the sync and refuses webhooks; the stripe schema stays until you drop it.