Back to Blog

Why Automatic Reconciliation Should Be a Default, Not a Feature

Abstract automation flow with checkmarks suggesting automatic data matching

The pattern we see most often in growing payment platforms is that reconciliation gets added as a feature after the core payment flow is working. It becomes the job of a finance analyst at month-end, or a script that runs nightly, or a report pulled from provider dashboards. That works for a while. Then it stops working, usually at the worst possible moment.

Where the "feature" framing comes from

When a platform first starts processing payments, reconciliation feels like an accounting concern rather than an engineering one. The engineering team builds the payment flow: API call to provider, response handling, database write, webhook listener. Reconciliation is the downstream step that finance handles once money is moving.

This split happens for understandable reasons. Payment engineering and financial operations are usually different people with different tooling. The payment system knows what it sent to the provider. Finance knows what came back in the bank statement. Connecting those two views feels like a problem for later.

The problem is that "later" compounds. By the time a platform has three providers, daily transaction volumes in the thousands, and month-end close pressure, reconciliation has become a part-time job for at least one person. That person's main task is pattern-matching payment IDs across spreadsheets exported from three different provider dashboards. At this point, reconciliation is genuinely expensive, even if it never showed up as a line item in the engineering budget.

What "automatic" actually means

Automatic reconciliation is not a reconciliation report that runs on a schedule. The difference is where the reconciliation logic sits relative to the payment event.

In the feature model, reconciliation happens after the fact: payment settles, data sits in the provider's system, reconciliation script pulls it later, matches against internal records, surfaces discrepancies. The process is asynchronous in a way that is not by design. It is asynchronous because no one thought about it at write time.

In the default model, reconciliation is a byproduct of how the payment event is recorded. When the routing layer writes the payment, it also writes the reconciliation record. The reconciliation event is emitted at the same time as the payment event. There is no "later" because the matching is structural, not procedural.

The design shift is this: reconciliation stops being a process you run and becomes a constraint you enforce at write time. That sounds like a small change. The operational difference is significant.

The identifier problem

The single biggest obstacle to automatic reconciliation is identifier management. Most payment providers return their own transaction IDs, which are unrelated to your internal transaction IDs. If you route through three providers, you have three identifier namespaces that all need to map to your internal record.

Platforms that do not solve this early end up with soft references: a column in the database that stores the provider's transaction ID as a string, matched to your internal ID by a foreign key that may or may not be enforced. When the provider later sends a settlement report or a chargeback notification referencing their ID, your system has to do a lookup that can fail if the original write failed or was incomplete.

Automatic reconciliation requires a structured identifier bridge. At the moment you submit a payment to a provider, you write three things in a single atomic operation: your internal payment ID, the provider identifier, and a reconciliation record ID that links them. Any event that arrives later, whether a webhook from the provider, a settlement file, or a bank statement, can be matched deterministically because the bridge exists before the payment completes.

The reconciliation record is not the payment record. It is a separate entity designed specifically to be matched against external events. Its schema is shaped by what matching requires, not by what payment routing requires. Those are different shapes.

Event-driven matching

The other piece that makes reconciliation automatic is treating provider events as a stream rather than a data source to poll. Provider webhooks, settlement notifications, and statement files are all events. When each event arrives, the reconciliation layer processes it immediately: find the reconciliation record with the matching provider transaction ID, verify the amount and currency, update the status, flag any discrepancy.

This is different from a batch matching job. A batch job processes all events at once, at a fixed interval. An event-driven approach processes each event as it arrives. The practical difference is that discrepancies surface within seconds of the provider event, not at the end of the day.

For a platform running a few hundred transactions a day, this mostly feels like a background process. For a platform running several thousand transactions a day, the gap between real-time and batch reconciliation is the gap between a 10-minute investigation and a multi-hour one.

There is a complication: provider webhook delivery is not perfectly reliable. Some providers deliver webhooks promptly and consistently. Others are slower or occasionally miss events. An event-driven reconciliation system still needs a fallback: periodic polling of provider APIs to catch events that were not delivered via webhook. This adds complexity, but it is bounded complexity, handled once at the infrastructure level rather than by each team that integrates a provider.

Handling exceptions

Automatic reconciliation does not mean no human involvement. It means human involvement goes to the right problems. The exceptions that need attention fall into a few categories.

Timing edge cases arise when the provider's settlement timestamp differs from your write timestamp across a day boundary. A transaction submitted at 23:58 Singapore time may settle in the provider's books for the following day. Your system sees it as Day 1; their report shows Day 2. This is not a discrepancy. It is a known timing pattern that the reconciliation layer should classify as "timing difference, resolved" rather than "unmatched."

Partial settlements happen when a provider settles a batch at a net amount rather than transaction by transaction. Your reconciliation layer needs to be able to match one settlement record to multiple payment records, which requires a different matching path than the 1-to-1 case.

True discrepancies are amount mismatches that cannot be explained by timing or batching. These are the ones that warrant human review. A well-designed automatic reconciliation system surfaces these clearly, with enough context (original payment, expected amount, received amount, provider reference) that the finance team can investigate without having to reconstruct the context themselves.

When we talk to teams that have done this well, the consistent feedback is not just about time savings. It is about predictability. Month-end close is predictable when you know the reconciliation system has been working continuously and the exceptions queue contains only genuine outliers. That is different from month-end close being an event where you discover how well the month's transactions matched.

The trade-off you are accepting

We are not claiming this is a simple retrofit. If you are adding automatic reconciliation to an existing payment system, you are taking on real engineering work: defining the reconciliation record schema, backfilling identifier bridges for historical payments, setting up the event processing pipeline, building the exception classification logic. That is weeks of work, not days.

The argument for starting this way from the first transaction is exactly that: backfilling is harder than designing it correctly upfront. The identifier bridge that takes an hour to add to a new system takes weeks to retrofit into one that has been running for two years, because every place that touches payment IDs needs to be updated consistently.

We built reconciliation as a first-class entity in Checker from the beginning. Every payment call through our routing API generates a reconciliation record at creation time. Settlement events are matched against those records automatically, using deterministic matching logic rather than heuristics. The finance team's view is a read-only interface on top of that infrastructure: matched records, pending records, and a discrepancy queue that surfaces only genuine exceptions.

The goal is not to remove reconciliation from the finance team's responsibilities. It is to make sure that when they do touch reconciliation, they are making judgment calls that require judgment, not spending time on matching that a system should handle reliably every time.

Build on Checker

Payment routing, reconciliation, and compliance for Southeast Asian fintech platforms. Start free, scale as you grow.

Get API Key Free