Payment reconciliation process: a complete guide
Talk to Formance
Talk to Formance about designing a ledger for payment reconciliation for your engineering and finance teams.
Talk to Formance
Talk to Formance about designing a ledger for payment reconciliation for your engineering and finance teams.
At month-end close, the same payment shows three different states: one in the PSP dashboard, one on the bank statement, and one in your internal ledger.
Reconciliation is the work of collapsing those three into one, and the architecture behind it decides whether a month-end close takes hours or turns into a week of reverse-engineering payouts.
This guide covers the reconciliation workflow, what breaks it at high volume, the ledger architecture that keeps it reliable, and the design principles that enable automation for engineering and finance teams.
Payment reconciliation verifies three things at once:
In practice, it compares internal records (the expected money movement) against bank and provider data (the actual money movement). It checks each posting against external evidence, flagging gaps, duplicates, misdated credits and orphaned values.
Those three verifications map to three reconciliation layers.
Transaction-level reconciliation matches each posting to its external event by reference ID, then validates the amount and effective date. This layer catches mismatched timestamps, misallocated postings, fee differences, and unexpected charges that aggregate matching can miss.
Account-level reconciliation confirms that the running balance in your ledger equals the provider's statement balance. It catches chargebacks and cutoff-time variations, and it surfaces safeguarding failures where the ledger disagrees with the PSP report and the bank statement introduces a third balance.
Position-level reconciliation tracks net exposure per asset per rail, so a multi-rail platform knows exactly how much value is in transit with each provider at any given moment.
The three layers are not interchangeable, and for regulated platforms, account-level reconciliation alone is insufficient. Two transaction-level errors that offset each other (like a duplicate posting or a missing fee posting) can net to zero in aggregate. The balance check passes while real discrepancies hide underneath.
Granular fee-level reconciliation can surface previously unquestioned provider charges that aggregate balance checks miss, which is why we built Formance as an open-source, programmable core ledger that unifies fiat and digital assets with regulatory-grade traceability across all three layers.
Ingesting raw provider data, normalizing it to a common schema, running transaction-level matching, flagging exceptions, investigating and resolving them, then finalizing the period are the six steps that run in a reconcildaion workflow.
Pull PSP settlement files, bank statements via API or SFTP, custodian feeds, and internal ledger exports. Store raw provider data in its original format before any normalization to preserve the raw evidence record. That untouched data is what you fall back to when a mapping breaks or an auditor asks how a figure was derived.
Provider exports often vary by field names, identifiers, date semantics, and cutoff conventions. Normalize identifiers and timestamp semantics, including field mappings, to your canonical model before comparison. Keep the original provider timestamp alongside the normalized one so downstream matching can still reason about timing differences.
Start with exact one-to-one matching by PSP transaction ID. Use batch aggregation by payout ID or cross-period linkage when refunds and chargebacks require it. Order rules from strictest to loosest, so weaker matches never override a stronger one that already succeeded.
Tag each exception with a specific type: timing difference, fee treatment, multi-currency effect, duplicate, or data error. The type determines the resolution path. Timing differences are clear on their own, while duplicates and data errors indicate an upstream fix is needed.
Recompute the net payout as gross minus fees minus refunds, locate the matching bank line, verify there are no duplicate postings, and assign an owner to each remaining delta. Record the fix as a new reversing posting rather than editing history in place, so the audit trail preserves both the error and its correction.
Close the period against adjusted balances with a documented adjustment record, including a narrative and supporting evidence, and date it. Lock the period so that no new postings can be applied to closed dates without an explicit reopening workflow.
Failure modes produce ledger states that no matching rule can resolve: non-idempotent retries, partial split-pay settlements, mid-settlement fee deductions, inconsistent FX revaluation, and orphan value from failed rollbacks. Each is upstream of reconciliation, meaning the fix is architectural rather than operational.
Retry and redelivery paths can cause the same business event to be retried multiple times. If idempotency is not enforced at the posting layer, duplicate postings can be written before reconciliation sees them.
When split-pay logic fails on one leg, a closed partner account or a rounding mismatch, the other leg may settle, and transactions can land in a suspense account awaiting manual untangling.
Some settlement reports present net deposits after fees. When you book only the net payout, the fee never gets its own posting, and that gap becomes a permanent variance.
If multi-currency balances are reconciled in the functional currency rather than the transaction currency, FX gain/loss differences can appear between books because some revaluation entries may appear only in the general ledger and not in the originating-currency subledger.
If a system assumes that a provider timeout indicates failure, it may reverse the debit even though the provider has already settled. That can strand value in the transit account with no matching business event.
Multi-PSP and multi-currency configurations multiply the failure surface described above. Each provider applies a fee and FX treatment on its own settlement-batching schedule, with its own rate sources and cutoff times. As platforms expand into more markets and add rails across currencies, they may need to reconcile multiple PSP and banking feeds. Those timelines must be normalized to a consistent reference clock before matching can begin.
A PSP collects $10,000 gross on your behalf, deducts a $250 PSP fee, and deposits $9,750 into your bank account. The example below shows how the full settlement, including the fee split, posts as a single atomic transaction, so both the fee line and the net deposit are queryable against the settlement report.
Numscript, a purpose-built language for describing the intent of financial transactions, expresses the settlement as one business event:
// PSP_SETTLEMENT
// Event: PSP settles $10,000 gross; $250 is recorded as PSP fees and $9,750 lands in the platform bank account
send [USD/2 1000000] (
source = @counterparties:paymentProviders:provider allowing unbounded overdraft
destination = {
max [USD/2 25000] to @platform:expenses:psp
remaining to @platform:banks:chase:main
}
)
set_tx_meta("event_type", "psp_settlement")
set_tx_meta("settlement_id", "stl001")
Each multi-leg Formance Ledger transaction commits atomically: all legs within that transaction are recorded, or none are. That makes reconciliation queryable at the settlement boundary. @counterparties:paymentProviders:provider gives the settlement boundary, while @platform:expenses:psp and @platform:banks:chase:main show total fees and the platform's bank credit. Fees plus net must equal gross, and the double-entry model enforces the identity structurally.
Under account-level reconciliation alone, the ledger shows $10,000 of gross activity; the bank statement shows a $9,750 credit. The $250 difference surfaces as an unexplained discrepancy, and someone opens an investigation.
Transaction-level reconciliation can resolve it in one match: the $250 posting on @platform:expenses:psp may pair against the fee line in the PSP settlement report, and only unmatched exceptions would reach a human review queue. Multiply that across thousands of settlements per month, and the difference between the two approaches is the difference between an exception queue that is signal and one that is noise.
Three architectural properties determine whether reconciliation can run automatically: bi-temporality to separate when money moved from when it was recorded, idempotency to prevent duplicate postings from retries, and immutability to preserve an auditable history.
All three must live in the ledger schema itself, because application-layer workarounds cannot backfill them later.
A bi-temporal ledger stores both the effective date, when money moved on the external rail, and the recorded date, when your system captured the event. Bi-temporal storage supports point-in-time balance queries and backdated corrections without rewriting history.
A settlement webhook that arrives on April 2 with a March 31 effective date must count in the March 31 balance while remaining recorded on April 2. Without that separation, every in-flight settlement reads as missing at cutoff.
When bi-temporality is in the schema, a cutoff query runs against effective dates using a point-in-time parameter, preventing in-flight settlements from generating phantom exceptions. The ledger schema requires this separation before reconciliation runs, as the configuration cannot add it later.
Idempotency at the posting layer must be preserved before retries reach the ledger. Retry logic on failed API calls to PSPs or custodians will resend requests. Without an idempotency key preserved with the original request and outcome, retries can create duplicate postings that are indistinguishable from legitimate transactions.
Once duplicate postings are recorded without an idempotency key, later matching becomes materially harder and may require manual investigation.
Immutability closes the loop for regulators. Each transaction produces a hash of its data combined with the previous transaction's hash, so any alteration to historical records breaks the chain and is immediately detected.
That property can support audit evidence for Digital Operational Resilience Act (DORA)-regulated entities and for client-asset safeguarding obligations under Article 75 of the Markets in Crypto-Assets Regulation (MiCA).
However, it does not, by itself, guarantee compliance with these regimes or eliminate the need for additional compliance tooling. Corrections happen as new reversing postings, never as edits, so the full history of debits, credits, and compensations is preserved for any auditor who asks.
Manual reconciliation shifts the cost of correctness from software onto headcount. In contrast, automated pipelines shift it back by making the ledger the single source of truth and reducing exceptions to real discrepancies.
| Dimension | Manual reconciliation | Automated reconciliation |
| Cost pattern | Finance and engineering time consumed by custom data mappings and repeated exception review; spreadsheet-based work introduces operational risk at high volume | Pipeline ingests raw provider data, normalizes to a canonical schema, and reconciles against the ledger with no per-close mapping work |
| Failure mode | Interface-level errors (e.g., Citibank wired about $893 million to Revlon creditors instead of the intended $7.8 million) can create irreconcilable states that downstream reconciliation detects but cannot resolve | Ledger-enforced constraints (double-entry, idempotency, and bi-temporality) prevent the malformed posting from being written in the first place |
| What it replaces | Brittle per-provider mapping scripts, month-end forensic spreadsheets, and human exception triage on schema drift | A single canonical model, an exception queue populated only by real discrepancies, and an engineering backlog freed from per-provider fixes |
| Production outcome | Ongoing headcount cost, close-week firefighting, exception queues dominated by noise | Liberis (Formance customer): zero reconciliation errors across 14 countries over 1.5 years in production |
The ledger layer must enforce reconciliation correctness from day one through programmatic double-entry constraints, with application-level validation as a secondary guard.
Some code paths can bypass application-level checks; ledger-enforced constraints reduce that risk. Five principles determine whether your ledger supports automated reconciliation.
Every transaction sums to zero, balances derive from postings, and the ledger programmatically enforces the invariant.
Bi-temporality is what separates timing differences from real discrepancies.
Carry the provider's transaction ID on every internal record as the primary match anchor; without it, you are matching only on amount and date.
Keep fees and FX out of adjustment entries. A fee that exists as its own posting reconciles against the fee line in the settlement report; a fee absorbed into a net figure becomes a permanent variance.
Drift detected before the month-end is prevention, whereas drift detected at the month-end is forensics. Emit alerts from configured reconciliation policies rather than waiting for batch reports at close.
Drift detected before the month-end is prevention, whereas drift detected at the month-end is forensics. Emit alerts from configured reconciliation policies rather than waiting for batch reports at close.
Teams often ask how often to reconcile. Set the maximum acceptable drift window for each payment rail based on the regulatory framework governing your platform and the liability exposure of each asset type.
For continuously settling rails, teams may choose shorter drift windows based on risk and regulatory exposure. Your rail mix and your license set the window; your architecture has to meet it.
Build the ledger so that reconciliation is a property of the data model, and the reconciliation engine confirms what the ledger has already enforced, rather than cleaning up drift after the fact.
That is what we built in Formance’s Ledger and Reconciliation modules to do: hold the canonical, bi-temporal record of every money movement and check it against your providers at configurable intervals, so drift surfaces earlier than month-end depending on your configured policies.