Your Postgres never drifts from Stripe.
Driftless is a single binary that receives Stripe webhooks, finds the events
Stripe never delivered, backfills history, and materializes everything into a
stripe schema in your own Postgres. Then it proves the result:
driftless verify reconciles your database against Stripe and
exits nonzero on drift.
Apache-2.0 · self-hosted · read-only against Stripe · no third party touches your payment data
| type | checked | drifted |
|---|---|---|
| product | 182 | 0 |
| price | 304 | 0 |
| customer | 41,209 | 0 |
| subscription | 8,417 | 0 |
| invoice | 96,532 | 0 |
| charge | 102,114 | 0 |
| total | 248,758 | 0 |
Webhook delivery is at-least-once, unordered, and silently droppable.
Stripe's own engineering blog says so, and teaches you to build queues and reconciliation jobs around it (their words). Teams discover the damage late:
- 3% of subscriptions drifted
- eight months of leaked revenue
- a webhook marked delivered that never arrived
And the evidence expires: Stripe's event log only goes back roughly 30 days. After that, a gap is not recoverable from events at all.
Ten minutes from zero to a proven-correct mirror.
You need Postgres 17+, a Stripe API key (a restricted read-only
rk_... key is recommended), and a webhook signing secret.
# 1. Install: one static binary, no runtime
curl -fsSL https://raw.githubusercontent.com/quyumkehinde/driftless/main/scripts/install.sh | sh
# 2. Point it at your Postgres and your Stripe account
export DRIFTLESS_DATABASE_URL='postgres://...'
export DRIFTLESS_STRIPE_API_KEY='rk_test_...'
export DRIFTLESS_STRIPE_WEBHOOK_SECRET='whsec_...'
# 3. First-run setup: environment checks, migrations, offer of a backfill
driftless init
# 4. Run it
driftless serve
# ...and locally, forward Stripe's webhooks to it
stripe listen --forward-to localhost:8724/webhooks/stripe
# 5. Prove it
driftless verify --full
Exit 0 means your database provably matches Stripe. Exit 3 means drift, with a
per-object report, and verify --repair fixes it. Put that exit code
in CI and billing drift becomes a failing check.
In production, add https://<your-host>/webhooks/stripe as an
endpoint under Developers → Webhooks in the Stripe dashboard, and subscribe
to the event types you mirror or to all events. Types Driftless does not model
are still stored, never dropped.
Prefer containers? A Docker Compose quickstart, a distroless image at
ghcr.io/quyumkehinde/driftless, and Kubernetes manifests ship in
deploy/.
Reads become SQL. Reactions become LISTEN/NOTIFY.
Anywhere you call the Stripe API or maintain hand-rolled billing state, query the mirror instead, and join it against your own tables:
-- can this user access the product?
SELECT s.status IN ('active', 'trialing')
FROM stripe.subscriptions s
JOIN app.users u ON u.stripe_customer_id = s.customer
WHERE u.id = $1 AND NOT s.is_deleted;
Anywhere you would write a webhook handler, listen instead. Every applied
change fires pg_notify on driftless_changes in the
same transaction as the write; your worker re-reads the row and acts. By
then the state is committed, deduplicated, and ordered: no payload parsing,
no signature checks, no duplicate or out-of-order handling in your code.
LISTEN driftless_changes;
-- {"type": "subscription", "id": "sub_123"}: read the row, act
What you delete from your codebase: the webhook endpoint, the dedupe table, the resync cron. The raw event log stays queryable past Stripe's 30-day window as your audit trail.
Everything between Stripe and your database.
- receive
- Signature-verified webhooks; Stripe is acknowledged only after the event is durably committed.
- dedupe
- Every event recorded exactly once; state converges regardless of duplicates, ordering, or same-second ties.
- gap sweep
- A sweeper finds events Stripe generated but never delivered, before the 30-day cliff erases them.
- backfill
- Full or incremental history, resumable across kill -9, including the canceled subscriptions the default listing hides.
- materialize
- Typed, indexed
stripe.*tables with the raw JSON preserved alongside. - prove
- A reconciliation report with an exit-code contract, and a repair mode that trues up whatever it finds.
At-least-once delivery, exactly-once processing, provable convergence. Nobody can give you exactly-once delivery, so that is what Driftless promises instead.
How it compares.
| Driftless | stripe/sync-engine | Hookdeck / Svix | Airbyte / Fivetran | |
|---|---|---|---|---|
| Dedupe guaranteed | yes | partial | best-effort | n/a |
| Out-of-order and same-second safe | yes | known open issue | no | n/a |
| Detects never-delivered events | yes | no | no | no |
| Backfill incl. canceled subscriptions | yes | known open issue | no | polling sync |
| Reconciliation proof | verify, exit-code contract | no | no | no |
| Where your data lives | your Postgres | your Postgres | their cloud, then yours | your warehouse, hours later |
Already running stripe/sync-engine? driftless import --from-sync-engine
migrates a database in place: one schema rename, one import, one
verify --repair.
Rather not be on call for it?
Everything that makes the mirror trustworthy lives in the open source binary and always will; a paid tier, if one ever exists, only adds hosting and dashboards on top. If that is the part you would pay for (fleet dashboard, drift history, alert routing), leave an email. No newsletter; one message if it ships.
You are on the list. No newsletter; one message if it ships.