Scheduled Stripe–Postgres reconciliation beats PR-only billing tests
Why Access checks belong on a cron, not every pull request — and how to install nightly billing drift detection in GitHub Actions.
Your PR checks pass. Stripe and Postgres still disagree in production.
That is normal. The Access contract compares live billing state to live database rows. Drift often does not exist at merge time.
Why PR gates miss billing bugs
| Scenario | PR-time check | After merge |
|---|---|---|
Refactor drops invoice.payment_failed handler | Pass (Stripe mocked) | Drift when card fails next month |
New price ID, stale plans: map | Pass (no customer on new price yet) | Drift on first signup |
Scheduled vs PR in the docs explains the split: file linters on PR, Access on a schedule.
What practitioners run
Alex Mayhew recommends a daily reconcile. Operational.co runs a daily cron. Same shape: list active Stripe subs, compare to DB access flags, alert on mismatch.
for await (const sub of stripe.subscriptions.list({ status: 'active' })) {
const row = await db.query(
`select has_paid_access from users where stripe_customer_id = $1`,
[sub.customer]
);
if (!row.rows[0]?.has_paid_access) {
await alert('drift', { customer: sub.customer });
}
}Thirty lines. Most teams defer it until support tickets arrive.
Install with ProdVerdict
npx prodverdict setup
npx prodverdict scheduled --frequency daily --installThat writes a GitHub Actions workflow. Secrets: read-only STRIPE_SECRET_KEY, read-only DATABASE_URL. Slack alert on FAIL.
Fixture demo (no keys):
npx prodverdict demoWhat a FAIL looks like
| Stripe | DB | Verdict |
|---|---|---|
active | has_paid_access = false | Revenue leak |
canceled | has_paid_access = true | Wrongful access |
Revenue leak locks out paying customers. Wrongful access gives away the product.
Full context: billing drift evidence. AI-specific failure modes: revenue leak from agent refactors.
PR checks still matter
Run config, migration, boundary, and webhook contracts on PR. They catch the handler you are about to ship broken. Scheduled Access catches the handler that broke three Tuesdays ago.
— mattbaconz
Try it in 60 seconds
Run the fixture demo — no Stripe key or database required.
npx prodverdict@latest check access --config examples/nextjs-stripe/prodverdict.yml --fixtures --fixtures-dir examples/nextjs-stripe/scenarios/fail-revenue-leakRelated posts
Stripe and Postgres drift apart. Here's the proof.
Webhooks are not enough. Stripe docs, practitioner writeups, and reconciliation patterns for when billing state and your database disagree.
June 29, 2026
Stripe Entitlements migration checklist (before you cut over)
Moving from Postgres paid flags to Stripe Entitlements? Pitfalls, dual-write traps, and how to verify billing vs DB before you flip the switch.
June 30, 2026
Stripe webhook idempotency in production (checklist)
Raw body verification, evt_ dedup, return 200 before slow work — the Stripe webhook patterns that stop duplicate charges and silent drift.
June 30, 2026
Comments
Guest comments. No account required. Max 300 words.
Loading comments…