Guide

Reconciliation compares two records of the same money: yours and the PSP's. The two will sometimes disagree, because a webhook is late, a refund settles tomorrow or a network call failed halfway. Reconciliation finds each difference, sorts it into a category, and sends the unexplained ones to a person or a bug queue.

The big idea

Run the comparison on a schedule, such as nightly, or continuously for high volume. Match each payment by its shared ID, such as a payment intent or charge ID. Anything that matches within the settlement window is fine. Anything that does not match is either expected lag or a real problem.

Walking through it

The process has four steps:

  1. Import the PSP's settlement file or API pages for the period.
  2. Match each record on the shared ID.
  3. Flag each mismatch: missing locally, missing at the PSP, amount mismatch, or duplicate.
  4. Route the flags. Engineering automates the obvious cases, such as a late webhook that should post to the ledger. Finance resolves the rest.

Here is the matching logic with four small records:

Python
ledger = {"pi_1": 4200, "pi_2": 1500, "pi_3": 900}
psp    = {"pi_1": 4200, "pi_2": 1600, "pi_4": 700}

for pid in sorted(set(ledger) | set(psp)):
    if pid not in psp:
        print(pid, "missing at PSP")
    elif pid not in ledger:
        print(pid, "missing locally")
    elif ledger[pid] != psp[pid]:
        print(pid, f"amount mismatch: {ledger[pid]} vs {psp[pid]}")
    else:
        print(pid, "matched")

Output:

pi_1 matched
pi_2 amount mismatch: 1500 vs 1600
pi_3 missing at PSP
pi_4 missing locally

Each line means something different. pi_1 is clean. pi_2 has the wrong amount, and someone must look at it. pi_3 is in our ledger but not in the PSP's file, so money we recorded may never have moved. pi_4 is in the PSP's file but not ours, so a payment may have happened without our system hearing about it.

Discrepancies are normal during lag. Unexplained ones after the settlement window closes are bugs or fraud. Treat reconciliation as a product feature with its own dashboard, not as spreadsheet work.

Common mistakes

  • Doing it by hand. Manual reconciliation does not scale, and it misses gaps.
  • Auto-correcting without matching. A fix that is not tied to a matched record can create a new error.
  • Ignoring the settlement window. Flagging a payment that is still settling creates noise.

Check yourself

  1. Why is pi_3 flagged as missing at the PSP, and not marked as matched?

    Answer: our ledger has a payment the PSP's settlement file does not list, so the money we recorded may never have moved.

  2. When is a mismatch a bug rather than lag?

    Answer: after the settlement window has closed, an unexplained difference is a bug or fraud.

Key takeaways

  • Reconciliation compares your ledger with the PSP's records, match by match.
  • Sort every difference: matched, missing on one side, or amount mismatch.
  • Expect differences during lag, and treat unexplained ones after the window as incidents.
  • Build reconciliation into the product, with an exceptions queue and a dashboard.

Further learning

Return to the payment system overview to see how reconciliation fits with the rest of the design.