Prompt
I'm building a subscription platform and need invoice status sync
Latest observation
Oct 5, 2026
OpenAI APIWeb search: off
A solid invoice status sync usually needs to handle webhooks + periodic reconciliation so your app stays correct even when events are delayed, duplicated, or missed.
Recommended approach
1) Define your invoice states
Keep a small internal state machine, for example:
draftopenpaiduncollectiblevoidrefunded(if you track this separately)past_due/overdue(if your billing provider distinguishes it)
Map provider statuses to your internal ones.
2) Use webhooks as the primary source of updates
Listen for invoice events from your payment provider, such as:
- invoice created
- invoice finalized/opened
- payment succeeded
- payment failed
- invoice voided
- invoice marked uncollectible
- refund/credit note issued
When a webhook arrives:
- verify signature
- deduplicate by event id
- fetch the latest invoice object if the event payload is partial/stale
- update your local invoice record
- emit internal domain events if needed
3) Reconcile periodically
Run a background job to sync invoices that may have been missed:
- every few minutes for recent invoices
- nightly for all unpaid/open invoices
- after webhook delivery failures
- after retry exhaustion
Compare provider status vs local status and correct drift.
4) Store sync metadata
For each invoice, store:
provider_invoice_idprovider_statuslocal_statuslast_synced_atprovider_updated_atlast_event_idsync_versionoretagif available
This helps you avoid overwriting newer data with older events.
5) Make updates idempotent
Invoice sync should be safe to run multiple times.
Recommended pattern:
- process event only once
- use
updated_ator event sequence to ignore stale updates - use database transactions
- guard status transitions, e.g. don’t move
paid -> open
6) Handle edge cases
Watch for:
- partial payments
- failed payment retries
- chargebacks/disputes
- credit notes/refunds
- manual voids
- subscription cancellations that leave invoices open
- timezone issues for due dates
- race conditions between webhook + polling
Simple sync flow
- Webhook arrives for
invoice.payment_succeeded - Verify signature
- Check event id hasn’t been processed
- Retrieve invoice from provider API
- Map provider status to internal status
- Update invoice row in DB
- Update subscription/billing entitlements if invoice is now paid
Example internal sync pseudocode
async function syncInvoiceFromProvider(providerInvoiceId: string) {
const providerInvoice = await billingApi.getInvoice(providerInvoiceId);
const localInvoice = await db.invoices.findByProviderId(providerInvoiceId);
if (!localInvoice) {
return db.invoices.create(mapInvoice(providerInvoice));
}
if (providerInvoice.updatedAt <= localInvoice.providerUpdatedAt) {
return localInvoice; // stale update
}
return db.invoices.update(localInvoice.id, {
providerStatus: providerInvoice.status,
localStatus: mapStatus(providerInvoice.status),
providerUpdatedAt: providerInvoice.updatedAt,
lastSyncedAt: new Date(),
});
}
If you want this to be robust, add:
- webhook signature verification
- event deduplication table
- retry queue
- dead-letter queue
- reconciliation worker
- audit log of status changes
If you want, I can help you design:
- a database schema for invoice sync,
- a webhook handler, or
- a status mapping table for Stripe/Paddle/Chargebee/etc.