apps/www/_blog/2025-12-19-stripe-sync-engine-integration.mdx
Today we are announcing a partnership with Stripe and official support for the Stripe Sync Engine in the Supabase Dashboard. Now, you get a one-click integration that syncs your Stripe data directly into your Supabase database. Query your customers, subscriptions, invoices, and payments using standard SQL.
This integration is the result of a collaboration between Supabase and Stripe. Stripe engineers contributed significant improvements to the open-source sync engine, including incremental sync, flexible JSONB storage, and a new CLI.
Any application that makes money has to incorporate payments into their application and has billing data worth exploring. Understanding your revenue means joining Stripe data with your application data: Which customers are on which plan? What features do paying users actually use? Which accounts are at risk of churning?
Traditionally, developers face two options:
The Stripe Sync Engine gives you a third option: click a button and query your Stripe data in SQL within minutes.
The sync engine keeps your Stripe data current through two mechanisms: webhooks for real-time updates and scheduled backfills for historical data. When you enable the integration, Supabase automatically configures both.
Once your data is synced, you can query it like any other Postgres table. Here are three scenarios we expect to be especially common.
Join your Stripe customers with your auth users to identify accounts that created a login but never started a paid subscription. This is the kind of query that's impractical with API calls but trivial with local data.
select
users.email,
users.created_at as signed_up,
now() - users.created_at as days_since_signup
from
auth.users
left join
stripe.customers on customers.email = users.email
left join
stripe.subscriptions on subscriptions.customer = customers.id
where
subscriptions.id is null
and users.created_at < now() - interval '7 days'
order by
users.created_at;
Aggregate your subscription data to see revenue broken down by product. With local data, this query runs in milliseconds regardless of how many subscriptions you have.
select
products.name as plan,
count(*) as subscribers,
sum(prices.unit_amount) / 100.0 as mrr
from stripe.subscriptions as subscriptions
join stripe.prices as prices
on prices.id = (subscriptions.plan::json->>'id')::text
join stripe.products as products
on products.id = prices.product
where subscriptions.status = 'active'
group by products.name
order by mrr desc;
Combine billing data with application usage to find paying customers who have gone quiet. This query joins three data sources that would require separate API calls and manual correlation without the sync engine.
select
customers.email,
subscriptions.current_period_end as renewal_date,
max(user_events.created_at) as last_active
from stripe.customers as customers
join stripe.subscriptions as subscriptions
on subscriptions.customer = customers.id
join public.user_events as user_events
on user_events.user_id = customers.metadata->>'user_id'
where subscriptions.status = 'active'
group by customers.email, subscriptions.current_period_end
having max(user_events.created_at) < now() - interval '30 days'
order by subscriptions.current_period_end;
Supabase already offers a Stripe Foreign Data Wrapper that lets you query Stripe data using SQL. The FDW is a thin layer that translates your SQL queries into Stripe API calls. When you run select * from stripe.customers, the FDW makes an API request to Stripe, transforms the JSON response into rows, and returns the results.
This works well for simple lookups. Need to check a customer's subscription status? The FDW handles it elegantly. But the abstraction breaks down when your queries get more complex or for queries that are critical to your application.
Consider a query that joins customers with subscriptions and filters by plan type. The FDW must make separate API calls for each table, fetch all the data into Postgres, and then perform the join locally. A query that takes milliseconds on local data can take seconds or minutes through the FDW. And if you're running this query frequently, you'll hit Stripe's rate limits.
The Sync Engine takes a fundamentally different approach. Instead of translating queries to API calls, it copies your Stripe data into actual Postgres tables. Your queries run against local, indexed data. Joins are fast. Aggregations are fast. There are no rate limits because you're not hitting an external API.
Ultimately, the FDW is great for occasional lookups and simple queries. The Sync Engine is built for applications that need billing data as a first-class part of their database.
There can be a lot of data to import from your Stripe account to your database, especially when you're setting up syncing with an existing account. To perform this backfilling process efficiently and reliably, the sync engine uses Supabase Queues (powered by pgmq) to sync data incrementally and in batches.
When you install the integration, the backfilling process will automatically start and spread the work across Edge Functions that can concurrently fetch data from the Stripe API and retry if there are any failures or rate limits.
Enable the Stripe Sync Engine from your Supabase dashboard:
The initial backfill will sync your historical data. Depending on your Stripe account size, this may take a few minutes to a few hours. Webhooks begin processing immediately, so new events are captured in real-time.
The Stripe Sync Engine is open source. Stripe engineers contributed improvements including:
View the source and contribute: github.com/stripe-experiments/sync-engine/
We're working with Stripe on additional improvements:
Start syncing your Stripe data today. Enable the integration from your Supabase dashboard.