Payments & Money APIs · Ledgers, Reconciliation & Risk
Reconciliation
Matching your records, PSP reports and bank statements every day.
Reconciliation proves that what you think happened matches what the PSP and bank say happened. Three-way match daily: internal records/ledger ↔ PSP balance transactions/settlement reports ↔ bank statements (payouts). Match by IDs (charge, refund, payout), compare amounts and currencies, and route exceptions (missing, mismatched, unexpected) to a queue with owners and SLAs. Common causes: webhook gaps, timezone cut-offs, FX, fees, partial captures and manual dashboard actions.
Reconciliation checks
| Match | Sources | Finds |
|---|---|---|
| Orders ↔ PSP charges | DB vs PSP list/report API | Missing webhooks, double charges, unpaid orders |
| PSP fees | Balance transactions | Unexpected fee changes |
| Refunds/disputes | PSP reports vs ledger | Manual refunds outside the app |
| Payouts ↔ bank | PSP payouts vs bank statement | Missing/returned payouts |
| Ledger invariants | Clearing account balances | Unbalanced or stuck flows |
async def reconcile_charges(day: date):
ours = {p.psp_id: p for p in await payments.succeeded_on(day)}
theirs = {t.source: t for t in stripe_balance_transactions(day, type="charge")}
for psp_id in ours.keys() - theirs.keys():
await exceptions.add("missing_at_psp", psp_id, day)
for psp_id in theirs.keys() - ours.keys():
await exceptions.add("missing_internally", psp_id, day) # e.g. lost webhook → backfill
for psp_id in ours.keys() & theirs.keys():
if ours[psp_id].amount_minor != theirs[psp_id].amount:
await exceptions.add("amount_mismatch", psp_id, day)
SELECT currency, SUM(amount_minor) AS outstanding
FROM ledger_postings p JOIN ledger_entries e ON e.id = p.entry_id
WHERE account = 'psp_clearing' AND e.occurred_at < now() - interval '5 days'
GROUP BY currency HAVING SUM(amount_minor) <> 0; -- should be zero once payouts land
Common pitfalls
- Reconciling only monthly.
- Timezone cut-off mismatches between systems.
- Exceptions with no owner.
Key points
- Three-way match daily
- Exceptions queue with SLAs
- Clearing accounts should net to zero
Further reading: Stripe: Payout reconciliation report
Practice questions
What is three-way reconciliation for payments?
Answer
Matching internal records, PSP transaction/settlement data and bank statements so every sale, fee, refund and payout agrees.
Name two common causes of reconciliation mismatches.
Answer
Missed webhooks (internal state behind the PSP) and refunds or captures performed manually in the PSP dashboard; also FX and timezone cut-offs.
Open in the app to mark lessons complete, take notes and track your progress.