Event-Driven Reconciliation
bankingJuly 10, 2026

Event-Driven Reconciliation

Solving the Payments Ledger Matching Problem at Scale

Reconciliation is the part of payments infrastructure that everyone tolerates and no one wants to own. It sits downstream of the systems that get architectural attention — settlement, clearing, ledger posting — and it's usually the last place teams look when they're modernising. That's a mistake. The batch job that matches transactions across your ledger, your payment processor, and your correspondent bank at 2 AM is often the single biggest source of operational risk in the payments stack, and it's rarely built with the same rigor as the systems it depends on. 


Why Batch Reconciliation Breaks Down 

The traditional model is familiar: transactions accumulate throughout the day across multiple systems of record, a nightly batch job pulls extracts, applies matching rules, and produces a discrepancy report by morning. It works, until scale or velocity changes the equation. 


Three failure modes show up consistently in production: 

Detection latency compounds risk. A mismatch between your ledger and a payment processor's settlement file isn't just an accounting nuisance — it can mask a duplicate charge, a failed reversal, or a fraud pattern. Finding out twelve hours later means twelve hours of exposure, and in a DORA-supervised environment, twelve hours of an incident timeline you'll need to explain.

Batch windows don't scale linearly. As transaction volume grows, the nightly job doesn't just get slower — it starts colliding with other overnight processes competing for the same window. Teams respond by throwing compute at it, which delays the problem rather than solving it. 


All-or-nothing matching obscures partial failures. Batch reconciliation typically re-processes the full day's transaction set. A schema drift in one upstream feed, or a timing issue with a single correspondent bank, can silently corrupt matching for that entire batch run, and the failure isn't visible until someone reviews the output. 

The response to these failures shouldn't be a faster batch job. It should be a different architecture. 


The Event-Driven Alternative 

Streaming reconciliation reframes the problem: instead of comparing static snapshots at rest, you match events as they arrive. Every transaction, settlement confirmation, and ledger entry becomes an event on a Kafka topic, and Flink applies matching logic continuously as those events flow. 

Topic design

Separate topics per source system — ledger-postings, processor-settlements, correspondent-confirmations — rather than one unified transaction stream. This keeps schema evolution isolated per source and lets you apply source-specific deserialization and validation before matching logic ever runs. Partition by transaction ID or account ID, not by arrival time, so that events for the same transaction land on the same partition and preserve ordering guarantees where they matter. 

Stateful matching with Flink

This is where the architecture earns its complexity. Reconciliation is inherently a join across streams that don't arrive in lockstep — a ledger posting might occur seconds before or minutes after the corresponding processor settlement event. Flink's CoGroupFunction or interval joins handle this directly: you define a matching window (commonly 15 minutes to a few hours, tuned to your actual settlement SLAs) and let Flink hold state for unmatched events within that window, emitting a match the moment both sides arrive. 

Windowing strategy determines your false-positive rate

Too narrow a window, and legitimate settlement delays get flagged as discrepancies. Too wide, and you delay detection of genuine breaks. Session windows work better than fixed tumbling windows here, since settlement timing is inherently bursty and irregular rather than periodic — model the matching window around observed settlement latency distributions, not round numbers. 

Unmatched event handling is the actual product

A transaction that hasn't found its match by window expiry isn't a bug in the pipeline — it's the discrepancy you built the system to surface. Route unmatched events to a dedicated Kafka topic (reconciliation-exceptions) with full context attached: which side arrived, what timestamp, what the expected counterpart looked like. This becomes the direct input to your operations team's exception queue, replacing the morning discrepancy report with a live, continuously updating one. 

Exactly-once semantics matter more here than almost anywhere else in your stack

A duplicate-processed event in reconciliation doesn't just create noise — it can produce a false match or a false discrepancy on a real financial transaction. Configure Flink checkpointing with exactly-once guarantees and use Kafka transactional producers on the output side. This isn't a place to trade correctness for throughput. 


What Changes Operationally 

The shift from hours to seconds isn't just a latency improvement — it changes who responds to discrepancies and when. A batch-based reconciliation failure is discovered by an operations analyst reviewing a report the next morning. A streaming pipeline surfaces the same failure while the transaction is still fresh enough that remediation is straightforward: reversing a duplicate before it settles further, catching a fraud pattern before the second or third occurrence, flagging a processor-side data quality issue while the processor's own team still has full context on their end. 

This also changes your regulatory posture. Continuous reconciliation with sub-minute detection gives you a materially stronger answer when a supervisor asks how you monitor for settlement discrepancies — a live exception queue with timestamps is a different conversation than "we review a report each morning." 


Where AI Tooling Fits: Prompting for Matching Rule Generation 

This build sits naturally in the "AI Prompting for Real Banking Systems" series, because matching logic is exactly the kind of code where Claude and GitHub Copilot earn their keep — and exactly the kind where an imprecise prompt produces something that looks correct and isn't. 


The failure mode to prompt against: asking for "reconciliation matching logic" without specifying tolerance rules produces code that does exact-match comparison on amounts, which breaks the first time currency rounding or fee deduction is involved. A workable prompt pattern specifies the tolerance explicitly — matching window duration, acceptable amount variance (e.g., rounding tolerance in minor currency units), and the exact fields eligible for fuzzy matching versus fields requiring exact equality (transaction ID should never be fuzzy-matched; settlement amount might tolerate a defined variance). 


Use Copilot inline for the boilerplate — Flink KeyedCoProcessFunction scaffolding, Kafka consumer configuration, serialization boundary code — where the pattern is well-established and low-risk. Reserve Claude for the matching rule design itself: reasoning through edge cases (partial settlements, reversed transactions, multi-currency conversion timing) benefits from a model that can hold the full rule set in context and flag where two rules might conflict, rather than generating one function at a time without seeing the whole picture. 


Whichever tool generates the matching logic, treat the output as a draft for review, not a merge candidate — the reviewer needs to independently verify tolerance thresholds against your actual settlement SLA data, not just check that the code compiles. 


The Build Decision 

Streaming reconciliation is a bigger lift than the batch job it replaces — it requires real Flink expertise, careful windowing design, and a team willing to own state management complexity that a nightly cron job never demanded. But the cost of the batch alternative isn't zero; it's just deferred and invisible, showing up as detection latency, operational fire drills, and audit findings rather than as an engineering line item. For institutions processing meaningful payment volume, the streaming architecture isn't a nice-to-have modernisation — it's closing a gap between what your compliance posture claims and what your monitoring can actually detect in real time.