Reconciliation Is Where the Money Hides
Every fintech has a ledger and a bank account, and the numbers between them disagree more often than anyone admits.
Your ledger says $84,219.47. Your bank says $84,103.24. The difference is $116.23.
Is that a rounding error across 50,000 transactions? A fee your processor deducted that your system didn't record? A refund that posted to the bank but never hit your ledger? Or is it the first symptom of a bug that has been silently misattributing money for six weeks?
You can't tell from the number. That's the problem with reconciliation. The gap between two systems never explains itself.
Why reconciliation is harder than it looks
Most engineers encounter reconciliation as a matching problem: take two lists of transactions, find the pairs, flag the mismatches. An afternoon of SQL and a cron job, maybe a weekend project.
But matching is the easy part. The hard part is what happens after matching fails. A $0.01 difference across a million transactions could be one missing penny or a thousand offsetting errors that happen to net to one cent. A transaction that appears in your ledger but not in your bank statement could be a timing issue that resolves tomorrow, or it could be a payment that never actually settled.
The engineering challenge is building a system that distinguishes between a break that will fix itself and a break that is hiding real money. Synapse Financial identified a $13 million discrepancy in September 2023, classified it as something that would resolve, and kept operating. By the time the bankruptcy trustee examined the records, the gap was $85 million.
The three-way problem
Two-way reconciliation compares your ledger against one external source. It confirms whether your records agree with someone else's records. But two wrong systems can agree just as easily as two right ones.
Three-way reconciliation compares your internal ledger, the processor's settlement file, and the bank statement. Each one sees the same money through a different lens.
Your ledger records intent. When a customer initiates a $500 transfer, you write an entry immediately. The ledger knows what you told the payment rail to do.
The processor's settlement file records acknowledgment. Stripe, Adyen, or your ACH originator sends a file confirming which transactions they accepted, which they rejected, and what fees they deducted. This file might arrive hours after the API call.
The bank statement records reality. The actual dollars that landed in or left your account. This is the ground truth, and it arrives last, sometimes days after the transaction.
When all three agree, the transaction is clean. When two agree and one doesn't, you know which system has the problem. When all three disagree, you have a different kind of problem entirely.
What breaks look like
Breaks cluster into patterns, and each type points to a different root cause and a different fix.
Timing breaks are the most common. A card authorization hits your ledger on Tuesday, the processor settles it on Wednesday, and the bank posts it on Thursday. On Tuesday night, your reconciliation sees a ledger entry with no matching settlement. On Wednesday night, it sees a settlement with no matching bank deposit.
By Thursday, everything aligns. These breaks are noisy but self-resolving, and most reconciliation systems handle them with a configurable waiting window.
Fee breaks are subtler. Your ledger records a $100 charge. The processor's settlement file shows $97.10 after deducting a 2.9% processing fee. The bank deposit reflects $97.10.
If your system matches on amount, the $100 ledger entry will never match the $97.10 deposit. You need to either record the fee as a separate ledger entry at the time of the transaction, or your matching logic needs to understand fee structures per processor.
One-to-many breaks appear when a processor batches multiple transactions into a single bank deposit. You have 847 individual ledger entries. The processor's file lists 847 settlements. The bank shows one deposit of $412,309.15.
Your matching engine needs to group the 847 entries, sum them, subtract expected fees, and compare the total against the single bank line item. If the totals don't match, you know there's a break somewhere in those 847 transactions, but not which one.
Missing transaction breaks are the most dangerous. A transaction exists in one system but not in another, and it's not a timing issue. The processor accepted a payment and sent a webhook, but your system never recorded it. Or your system recorded a refund, but the processor never processed it. Every missing transaction is real money that one system knows about and another doesn't.
Penny breaks seem harmless. A $0.01 difference on a single transaction is usually a rounding artifact from fee calculations or currency conversions. But penny breaks compound. If your fee computation rounds differently than your processor's, every transaction is off by a fraction of a cent, and over a month of high volume, those fractions add up to real dollars.
The matching engine
Most reconciliation systems run matching in layers, each one catching what the previous layer couldn't.
The first layer does exact matching on a unique identifier: a transaction ID, payment reference, or idempotency key. In a well-instrumented system, this catches the vast majority of records.
If exact matching catches significantly less, the problem is usually inconsistent identifiers. Your system calls it txn_abc123, the processor calls it PAY-abc123, and the bank statement truncates it to abc12. Identifier normalization, stripping prefixes, uppercasing, removing dashes, is often the hardest part of the matching pipeline.
The second layer handles one-to-many and many-to-one groupings. A single bank deposit might cover hundreds of processor settlements, and multiple customer payments might clear a single invoice. This layer groups records by date range and amount, then checks whether subsets sum to a known total.
The combinatorial problem grows fast. Ten unmatched transactions on each side produce over a thousand possible groupings to check.
The third layer applies fuzzy matching for records that lack clean identifiers: amount within a tolerance, date within a window, and any available metadata like merchant category or customer reference.
Fuzzy matching is necessary but risky. A tolerance of $0.02 seems safe until two unrelated transactions happen to be $0.01 apart and get paired incorrectly. Every false match hides a real break.
What's left after all three layers is the exception queue.
The exception queue is the product
For a mature reconciliation system, the exception queue matters more than the matching algorithm. This is where ops teams spend their time, and how the queue is designed determines whether breaks get resolved or accumulate.
A raw list of unmatched transactions is useless at scale. An ops analyst looking at 200 items needs to know which ones matter. A $0.01 timing break that will auto-resolve tomorrow and a $50,000 missing settlement need different levels of urgency.
Break categorization at creation is what makes the queue navigable. Each break gets tagged: timing, fee, partial, missing reference, amount mismatch, duplicate.
The tag determines the routing. Timing breaks get a 48-hour auto-resolve window, fee breaks route to the processor integration team, and missing transactions escalate immediately.
Ownership matters as much as categorization. A break between your ledger and the processor's file is your engineering team's problem. A break between the processor's file and the bank statement is the processor's problem, or your treasury team's. A break between your ledger and the bank that the processor's file can't explain is everyone's problem.
Aging is the third dimension. A break open for 24 hours is probably a timing issue, but one that lingers for 14 days is likely a bug, and anything unresolved past 90 days becomes a compliance exposure. Aging-based escalation, where unresolved breaks automatically move up the severity ladder, prevents the slow accumulation that turned Synapse's $13 million discrepancy into an $85 million hole.
The ops teams that handle this well treat the exception queue as the system's primary output, not something tacked on after the matching engine ships.
Batch versus streaming
Most reconciliation runs overnight. A cron job pulls the processor's settlement file, downloads the bank statement, loads both into a staging table, and runs the matching engine. Results land in a dashboard by morning.
The problem with batch reconciliation is the delay. A transaction that settles at 9am Tuesday won't be reconciled until Wednesday morning. If the transaction failed, silently, because the processor accepted it but the bank rejected the ACH, you won't know for at least 12 hours. In that window, your ledger says the customer has money that the bank never received.
This is exactly how fail-open event systems create reconciliation problems. The system assumed success, showed the customer a balance, and now you're discovering the failure half a day later.
Streaming reconciliation matches each event as it arrives. A processor webhook fires, and within seconds your system compares it against the ledger entry and any available bank notification. Breaks surface in near real-time instead of the next morning.
But streaming reconciliation introduces its own complexity. Events arrive out of order. A bank notification for a Tuesday settlement might arrive before the processor's Wednesday settlement file that explains the individual transactions within it. Your matching engine needs to handle partial matches, where one side of the pair exists but the other hasn't arrived yet, without generating false breaks.
Idempotency becomes critical. If a processor sends the same webhook twice, or the bank notifies you of a deposit that your streaming matcher already paired, the system needs to recognize duplicates without double-counting. In 2012, Nationwide Building Society processed a batch of debit card transactions twice, double-debiting 704,426 accounts because their reconciliation layer didn't catch the duplicate batch before it posted. This is the same idempotency problem that payment systems face, applied to the reconciliation layer.
The practical answer for most fintechs is hybrid: streaming reconciliation for high-priority flows like card authorizations and instant payments, batch reconciliation for ACH settlements and end-of-day bank files. The two systems need to agree on which transactions they've each processed, or you end up reconciling your reconciliation systems.
What the architecture reveals
Reconciliation is often treated as an ops function, something that happens after the real engineering work is done. But the quality of your ledger design determines how hard reconciliation will be.
If your ledger records a single timestamp per transaction, your matching engine has to guess which clock to match against: when the customer initiated, when the processor acknowledged, or when the bank settled. Recording all three gives the matcher unambiguous join keys.
The same principle applies to fees. A gross-amount-only ledger forces the matching engine to reverse-engineer fee structures from processor documentation, while a ledger that records gross, fee, and net as separate entries makes fee breaks trivially detectable.
Identifiers matter even more. If your ledger generates its own transaction IDs with no correlation to processor references, you're stuck in fuzzy matching territory. Storing the processor's transaction ID alongside your own gives the exact matcher a clean join on the first pass.
The most reliable reconciliation systems are the ones where the ledger was designed with reconciliation as a first-class constraint. The matching algorithm is a downstream consequence of that decision. Get the ledger right, and most of your breaks never happen in the first place.
Sources
- Modern Treasury: Defining Reconciliation Rules - Modern Treasury, reconciliation rule configuration, matching logic, expected payment setup
- Nationwide Building Society Double Debit Incident - IT Pro, 704,426 accounts double-debited in July 2012 due to duplicate batch processing
- CFPB vs Synapse Financial Technologies - PYMNTS, CFPB complaint over $60-90 million fund reconciliation gap, August 2025
- Modern Treasury: Expanding Support for Complex Reconciliation - Modern Treasury, one-to-many matching, fee handling, and advanced reconciliation rules
- Payment Reconciliation System Development Guide - Trio, three-way reconciliation architecture, break matching layers, exception handling patterns
- Microsoft Dynamics: Settlement Overview for Centralized Payments - Microsoft, penny difference tolerances, overpayment and underpayment settlement categories
- How Fintechs Handle Payment Exceptions in 2026 - Forest Admin, exception routing, break categorization, ops workflow design

Writes Ledger Drift. Engineer building AI systems and financial infrastructure. Open source contributor to x402, ACK, and MPP.
Frequently Asked Questions
Built by Trio, a fintech-native engineering partner helping teams build the next generation of financial technology and infrastructure.
Subscribe to Ledger Drift for high-signal insights into how modern fintech is built, from systems to code to teams.