Guides
Payment Sync
Stripe says the customer paid. Your database says they are still on the free plan. That gap — a paid Checkout session with no matching side effect — is the most expensive kind of silent failure. Payment Sync exists to catch it before support tickets arrive.
Payment Sync reconciles paid Stripe Checkout sessions against Supabase rows you configure as fulfillment proof. Drift means your webhook handler failed, ran late, or wrote to the wrong place.
What it reconciles
| Source | What it represents |
| --- | --- |
| Stripe Checkout sessions | Payments Stripe believes completed in the configured window |
| Supabase side effects | Rows your app wrote in response — subscription, entitlement, credit grant, etc. |
PreFlight examines up to 50 paid sessions per run within a default 24-hour window. Each session is matched by the trace column you map on the Stripe integration.
Drift types
| Kind | Meaning |
| --- | --- |
| `missing_side_effect` | Paid session with no matching Supabase row |
| `duplicate_side_effect` | Multiple rows for the same payment trace |
| `untraceable_payment` | Payment exists but trace ID cannot be resolved |
| `status_mismatch` | Row exists but status column does not match expected value |
Requirements
Connect Stripe and Supabase on the same project at `/dashboard/providers`.
Configure shadow mapping on the Stripe integration: priceId, verifyTableName, and verifyTraceColumn tell PreFlight where fulfillment rows land.
Run a snapshot manually from Payment Sync or wait for the monitoring cron to schedule one.
Payment Sync cannot guess your data model. Until you map the table and trace column that represent fulfillment, the feature returns a warning — there is nothing deterministic to reconcile against.
Optional verification fields on the Stripe integration refine matching:
| Field | Purpose |
| --- | --- |
| `verifyStatusColumn` | Column that must match `verifyExpectedStatus` |
| `verifyCreatedAtColumn` | Timestamp column for recency checks |
| `verifyWindowSeconds` | How long to wait for async side effects during shadow runs |
PreFlight can scan the connected Supabase OpenAPI schema and suggest the strongest mapping.
Reading a snapshot
Each Payment Sync run produces an immutable snapshot:
| Field | Meaning |
| --- | --- |
| Checked sessions | Paid Stripe sessions examined in the window |
| Drift count | Sessions with no matching or mismatched side effect |
| Status | `success` (no drift), `warning`, or `failed` by severity |
| Affected table | The Supabase table used for reconciliation |
| Open drift | Specific session IDs that need investigation |
Snapshots persist in the Event Timeline. Old snapshots are history — run a fresh snapshot after fixing drift to confirm it cleared.
Investigating drift
Confirm payment in Stripe. Open the flagged session ID in Stripe Dashboard and verify it reached paid.
Check webhook logs. Did checkout.session.completed arrive? Did your handler return 2xx?
Verify the mapping. Confirm verifyTableName and verifyTraceColumn match where your handler actually writes.
Backfill and re-run. Re-process the missed event or insert the side effect, then trigger a fresh Payment Sync snapshot.
Common root causes include webhook signature mismatch (STRIPE_WEBHOOK_PROBE_FAILED), wrong webhook URL, or writing the trace ID to a different column than mapped.
Scheduled reconciliation
The monitoring cron runs due Payment Sync snapshots on a configured cadence (REVENUE_LEDGER_WATCH_CADENCE_MS). When scheduled reconciliation detects blocking drift:
- Alerts route through configured Slack, Discord, email, or on-call channels.
- Release Rules with
require_revenue_watch: true begin blocking until drift clears.
A release rule can require a clean Payment Sync snapshot before promoting. That prevents shipping billing or fulfillment changes while reconciliation is already broken — see [Release Rules](/docs/guides/deploy-gates).
Troubleshooting
| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Feature shows warning, zero drift | Stripe or Supabase not connected, or mapping missing | Connect both providers and set verify fields |
| Every session shows drift | Wrong table or trace column | Re-check mapping against your webhook handler |
| Drift count won't clear | Side effect not backfilled | Insert rows for flagged session IDs, then run a new snapshot |
| Shadow passes, Payment Sync fails | Different trace columns or windows | Align shadow mapping with Payment Sync mapping |
<RelatedLinks
links={[
{ href: "/docs/guides/verify-stripe-checkout-without-a-real-credit-card", title: "Verify Checkout without a card", description: "Shadow flow that proves the same side effects." },
{ href: "/docs/integrations/stripe", title: "Stripe integration", description: "Webhook secrets, price IDs, and verify fields." },
{ href: "/docs/integrations/supabase", title: "Supabase integration", description: "Service role access for reconciliation." },
{ href: "/docs/api/monitoring-cron", title: "Monitoring cron", description: "Scheduled Payment Sync runs." },
]}
/>