A Fastify-based server for syncing your Stripe account to a PostgreSQL database in real time. Built on top of the Stripe Sync Engine.
- Exposes a
/webhooksendpoint to receive Stripe webhooks and sync data to PostgreSQL - Supports syncing customers, invoices, products, subscriptions, and more
- Runs as a lightweight Docker container
- Designed for easy deployment to any cloud or self-hosted environment
docker pull supabase/stripe-sync-engine:latestdocker run -d \
-e DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres \
-e STRIPE_SECRET_KEY=sk_test_... \
-e STRIPE_WEBHOOK_SECRET=... \
-e API_KEY="my-secret" \
-p 8080:8080 \
supabase/stripe-sync-engine:latestSet your webhook endpoint in the Stripe dashboard to point to your server’s /webhooks route (e.g., https://yourdomain.com/webhooks).
| Variable | Description | Required |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string (with search_path=stripe) |
Yes |
STRIPE_WEBHOOK_SECRET |
Stripe webhook signing secret | Yes |
API_KEY |
API key for admin endpoints (backfilling, etc.) | Yes |
SCHEMA |
Database schema name (default: stripe) |
No |
STRIPE_SECRET_KEY |
Stripe secret key (needed for active sync/backfill) | No |
PORT |
Port to run the server on (default: 8080) | No |
STRIPE_API_VERSION |
Stripe API version (default: 2020-08-27) |
No |
AUTO_EXPAND_LISTS |
Fetch all list items from Stripe (default: false) | No |
BACKFILL_RELATED_ENTITIES |
Backfill related entities for foreign key integrity (default: true) | No |
MAX_POSTGRES_CONNECTIONS |
Max PostgreSQL connection pool size (default: 10) | No |
REVALIDATE_OBJECTS_VIA_STRIPE_API |
Always fetch latest entity from Stripe instead of trusting webhook payload, possible values: charge, credit_note, customer, dispute, invoice, payment_intent, payment_method, plan, price, product, refund, review, radar.early_fraud_warning, setup_intent, subscription, subscription_schedule, tax_id | No |
DISABLE_MIGRATIONS |
Disable the automated database migrations on app startup (default: false) | No |
PG_SSL_CONFIG_ENABLED |
Whether to explicitly use the SSL configuration (default: false) | No |
PG_SSL_REJECT_UNAUTHORIZED |
If true the server will reject any connection which is not authorized with the list of supplied CAs. This option only has an effect if requestCert is true | No |
PG_SSL_REQUEST_CERT |
If true the server will request a certificate from clients that connect and attempt to verify that certificate. Defaults to false | No |
PG_SSL_CA |
Optionally override the trusted CA certificates | No |
PG_SSL_CERT |
Certificate chain in PEM format | No |
POST /webhooks— Receives Stripe webhook events and syncs data to PostgreSQLGET /health— Health check endpointPOST /sync— Backfill Stripe data to PostgreSQL (API key required)POST /sync/single/:stripeId— Backfill or update a single Stripe entity by ID (API key required)POST /daily— Backfill data from the last 24 hours (API key required)POST /weekly— Backfill data from the last 7 days (API key required)POST /monthly— Backfill data from the last 30 days (API key required)
version: '3'
services:
postgres:
image: postgres:17
restart: always
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
volumes:
- pgdata:/var/lib/postgresql/data
stripe-sync:
image: supabase/stripe-sync-engine:latest
depends_on:
- postgres
ports:
- 8080:8080
environment:
DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable&search_path=stripe
STRIPE_SECRET_KEY: sk_test_...
STRIPE_WEBHOOK_SECRET: whsec_...
API_KEY: my-secret
volumes:
pgdata:Note: The
/syncendpoints are NOT recommended for use if you have more than 10,000 objects in Stripe. For large backfills, it is best to write a script that loops through each day and sets thecreateddate filters to the start and end of day.
POST /sync
body: {
"object": "product",
"created": {
"gte": 1643872333
}
}
objectall | charge | customer | dispute | invoice | payment_method | payment_intent | plan | price | product | setup_intent | subscription | early_fraud_warning | refund | credit_note | tax_id | subscription_schedulescreatedis Stripe.RangeQueryParam. It supports gt, gte, lt, lte
POST /sync/daily
POST /sync/daily
body: {
"object": "product"
}
To backfill/update a single entity, you can use:
POST /sync/single/cus_12345
The entity type is recognized automatically, based on the prefix.