Let's first understand why reconciliation systems are necessary and why payment backends cannot function without them.

Let's say you built an email notification service that uses Resend, which in turn delivers via Gmail or Outlook. There are three systems involved in a single process: your system, Resend, and Gmail. For everything to work correctly, all three systems must agree on what happened. If they don't, you have inconsistency.

Why is this a problem in payments specifically, and not in other systems?

If you sent an email and your system marked it as sent, but an exception meant the email never actually went out, Resend or the email provider silently failed. In email, that's annoying. In payments, that's catastrophic.

Imagine Google Pay showing ₹1,000 debited while your bank account still shows the original balance. That's not a UI bug. It's a payment disaster waiting to happen.

In this analogy, Resend becomes a PSP (Payment Service Provider) such as Google Pay or PhonePe, and the email provider becomes an actual bank such as ICICI or SBI.

The Real Problem

Most payment bugs do not happen when the user taps Pay. They happen later.

The app says the payment succeeded. The PSP has one record. The bank has another. Your internal system has its own event. Now someone has to answer the uncomfortable question: did the money actually settle correctly?

That is what reconciliation systems exist for.

I recently built a payment backend and integrated reconciliation into it. It follows these steps:

  • Stores internal payments.
  • Ingests PSP and bank files.
  • Normalizes records into one format.
  • Runs matching logic across all three systems.
  • Creates exceptions with additional context for anything that does not line up.

A payment system has at least three views of the same transaction:

  • Your internal payment record.
  • The PSP settlement record.
  • The bank settlement record.

If all three agree, life is easy. If one is missing, delayed, duplicated, or inconsistent, someone has to investigate before money goes out of balance. That is why reconciliation is not a reporting feature. It is a correctness system.

How It Actually Works

You take receipts from your PSP, actual bank records, and your own ledger entries, then match them to confirm they are consistent. If they are not, you investigate further by checking ledger entries, contacting the PSP, or flagging the transaction for manual review.

The shape is practical:

  1. Internal transactions are written into the internal database.
  2. External files from PSPs and banks are ingested and stored on disk, parsed, and converted into normalized records.
  3. A reconciliation run compares internal rows, PSP rows, and bank rows line by line for a given provider and date range.

That comparison produces two outputs:

  • Reconciliation results.
  • Reconciliation exceptions.

That is the core loop.

Why Normalization Matters

Every PSP and bank records data in its own format. For example, a user might be referred to as a candidate, merchant, subscriber, customer, or visitor in different systems. We need a layer that normalizes this into one shared structure.

Once everything looks the same, matching becomes possible. Without normalization, reconciliation logic turns into a mess of one-off parsers and special cases.

File Idempotency

As discussed in the previous part of the blog, idempotency is one of the fundamental concepts when building payments systems, so our system needs duplicate-file handling.

If you haven't read part one yet, start here: Payment Backend 101 - From a Beginner's Perspective.

Every uploaded file is saved, hashed with SHA-256, and checked against previous ingests using the checksum. If the exact same file arrives again, the system does not ingest it twice. We are comparing thousands, or sometimes millions, of records across different systems, so we cannot repeat the work every time.

That matters even more because pipelines retry all the time:

  • Someone uploads the same file again, accidentally or intentionally.
  • An operator is unsure whether the first import worked.

If you do not make file ingestion idempotent, your reconciliation results become noisy very quickly.

What a Reconciliation Produces

If it produces zero mismatches, perfect. But we all know nothing is that perfect. If it encounters any inconsistency, it produces exceptions.

Here are some common reasons:

  • Timing differences: a transaction might appear in your accounting system before it shows up on the bank statement, or vice versa.
  • Data entry errors: typos or mistakes during data entry can lead to discrepancies that need to be resolved.
  • Missing transactions: a transaction might be missing from one set of records but exist in the other.
  • Bank fees or interest: these need to be accounted for and may not be reflected in your internal records initially.

After identifying the reason for the mismatch, you can take corrective action, such as updating your software or contacting the bank or PSP team for investigation.

Real systems are not failing in dramatic ways all the time. They are drifting in small, operationally expensive ways.

Exceptions Are a First-Class Output

One thing people miss when they first learn reconciliation is this:

The goal is not to eliminate exceptions. The goal is to surface them clearly, classify them correctly, and make them resolvable. You cannot avoid exceptions; they are guaranteed to happen.

We are dealing with three independent systems here: our own backend, a PSP, and a bank. Inconsistencies are bound to happen at some point. The real question is how we handle them.

Exceptions should be stored separately with an exception type, status, note, and resolved timestamp.

That matters because a reconciliation engine is only half the system. The other half is the operational workflow around resolving the exceptions generated by reconciliations.

Simply detecting a mismatch without resolving it is just a log line with better branding.

A Clean Mental Model

If you want the entire system in a few lines, it looks like this:

  • Internal payments enter the system.
  • PSP and bank files arrive through upload.
  • Files are deduplicated by checksum.
  • Rows are normalized into one schema.
  • A run compares internal, PSP, and bank records.
  • Matches are stored.
  • Mismatches become exceptions for follow-up.

That is reconciliation. Not easy to build, not easy to maintain, but absolutely necessary.