Payments reconciliation

Match bank and M-Pesa statement lines against recorded payments — import evidence, review what the engine could not decide, and sign a period off.

Finance → Reconciliation → /reconciliation/overview Beta

Reconciliation answers one question: does the money the bank says arrived match the payments the system recorded?

Everything else follows from that. A statement line with no payment behind it is money you have received but not credited to anyone. A payment with no statement line behind it is money you think you have but the bank has never seen.

statement imported → lines matched against payments → what the engine could not decide goes to a human → exceptions resolved → the period is signed off.

Before you start: two switches

Reconciliation is off until both of these are on. Neither reports an error when it is off — a run simply completes having examined nothing, which looks identical to "everything is already reconciled".

  1. Turn the module on for the organization

    The feature flag payments_reconciliation must be enabled for your organization or branch. Ask an administrator, or see Feature flags.

  2. Enrol each bank account

    Open the account under Bank accounts, press Edit, and switch on Reconcile this account's statements in the Integrations section. Accounts are enrolled one at a time on purpose: you reconcile the accounts you have statements for, not every account you have ever created.

    This is a different switch from Bank feed connected. That one says money lands on the account without anyone keying it in; this one says the account's statements are imported and matched. Reconciling a manually-captured account is the normal case, so an account can have either, both or neither.

If either gate is closed, every tab in the module carries a Reconciliation is not switched on yet banner naming the one that is missing — you do not have to work it out from a screen of zeros. A run with 0 lines when you know you imported a statement is the same symptom.

The tabs, and the order to use them

The tab strip is numbered for this flow, so where you are in it is never a guess:

TabWhat it is for
OverviewWhere you stand, and the one thing to do next. Not a step.
1. ImportsFile a statement, and see what every past file staged.
2. MatchingHow a line is decided, and the history of every sweep.
3. WorklistThe queue of lines that need a person.
4. ExceptionsThe typed findings.
5. PeriodsClose the month.

Reconcile now is pinned to the right of the tab strip, so a match run can be started from any tab.

Step 1 — Import a statement

Statements come in through the Bulk Upload wizard, not through a separate uploader on this page. The 1. Imports tab is the history of what has been filed.

  1. Open the wizard

    Bulk Upload → Bank Statements (for reconciliation).

  2. Choose the account and the format

    Both are required and neither can be guessed from the file — several banks export the same column layout, and filing a statement against the wrong account would attribute one organization's money to another.

    FormatUse it for
    M-Pesa paybill statementThe paybill export from the M-Pesa org portal. Has separate Paid In / Withdrawn columns and a Receipt No.
    NCBA statement (CSV)NCBA's CSV export.
    Generic statement (CSV, signed amount)A plain CSV with one signed Amount column — positive in, negative out. A separate withdrawn column is ignored by this format.
  3. Map the columns (usually nothing to do)

    The parser recognises the standard headers for each format. Map a column only when your export uses a name it does not recognise.

    The single most valuable column is the Receipt / Reference No. — the M-Pesa receipt or bank reference. An exact match on it reconciles a line automatically with no human step. Without it, every line needs a person.

Rows the parser cannot read are reported, never silently dropped. A statement routinely carries header blurb and subtotal rows, so one unreadable row does not cost you the other four hundred. The run summary lists each rejected row with its number and the reason — most often No readable transaction date or No readable amount, which means that column did not map.

If the import staged 0 lines, nothing was filed. The run is marked Failed, not Succeeded.

Reading the Imports tab

Each row is one file. Alongside the account it was filed against and the dates it covers, the row shows where its lines got to — how many auto-matched, how many are still waiting on a person, how many raised an exception. A row count on its own tells you the file landed; it does not tell you whether any of it reconciled.

Select a row to open it. The import expands into the lines it actually staged — date, amount, receipt or reference, payer, narrative and current state — and each line links through to its workbench. This is how you check what a file contained without going hunting in the worklist.

Step 2 — Matching

A match run starts automatically after an import, and you can start one any time with Reconcile now, pinned to the right of the tab strip. Each unresolved line is scored against the payments on that account within the date window. The 2. Matching tab states these three tiers on screen and lists every sweep that has run beneath them.

TierWhat it meansWhat happens
Tier 0 — exact receiptThe line's receipt number equals the payment's confirmation number.Reconciled automatically. This is identity, not a guess.
ScoredAmount, reference, payer phone and recency agree to a degree.Offered as a suggestion for a human to approve. Never auto-applied unless auto-approve is switched on and the score clears the threshold.
No candidateNothing plausible on that account.Raises an exception.

Confidence bands are 95 / 80 / 60. Auto-approve is off by default: the engine proposes, a person disposes.

Step 3 — Work the worklist

3. Worklist is the queue of lines that need a person. Open a line to reach the workbench, which puts the statement evidence beside the candidate payments and shows the per-component score for each one, so you can see why something was suggested.

From the workbench:

ActionUse when
ApproveThe suggested payment is the right one.
RejectThis candidate is wrong; the others stay on offer.
Match manuallyYou know the payment and the engine did not find it.
Post paymentThe bank received money the system never recorded. Creates the missing payment through the normal payment path, with a receipt derived from the line so a retry cannot double-post.
IgnoreThe line is not a payment — a bank charge, a transfer, a reversal. Requires a reason, which is kept on the audit trail.

Every one of these is recorded on an append-only trail with who did it and when.

Step 4 — Exceptions

An exception is a typed finding, not a generic error:

TypeMeaning
Missing in platformMoney on the statement with no payment recorded. Usually resolved with Post payment.
Missing in statementA payment with no statement evidence. Either the statement is incomplete or the payment is not real.
DuplicateTwo statement lines claim the same payment.
Amount mismatchThe reference agrees but the amount does not.
Already matchedA re-imported line conflicts with one already matched.
StaleUnresolved past the staleness threshold — nobody has worked it.

Step 5 — Sign the period off

5. Periods closes the loop. A period covers one bank account for one calendar month, and it cannot be signed off while anything in it is unresolved — that refusal is the control.

Where the dates come from

You never create a period. Importing a statement opens the calendar month containing the statement's earliest transaction, for that bank account, and files every line on that import into it. That is the whole rule, and it is why a month you did not ask for can appear: it appeared because a statement line was filed into it.

Each card names the account, the month it covers, the span of transactions actually filed into it, and how many imports opened it. Where those two spans disagree — a month covering 1–31 July holding transactions only up to the 12th — the statement is partial, and the rest of the month has no evidence behind it yet.

The Sign off button states what is blocking it rather than making you press it to find out: an unresolved line count, an open exception count, or no lines filed at all.

A signed period can be reopened by an administrator, but the reason is mandatory and is kept.

Re-importing the same statement

Safe. There are two guards:

  • Line level — each line is hashed on its identity fields, so the same transaction cannot be staged twice even if the file is re-exported with different column order or spacing.
  • File level — a file already staged for that account is recognised and skipped.

A row that was rejected is not fingerprinted, so fixing the file or the mapping and re-uploading imports it normally.

Troubleshooting

What you seeWhat it means
Run completed, 0 lines, no errorThe account is not enrolled, or the feature flag is off for its branch. See Before you start.
Import says 0 line(s) staged, N unreadableThe parser could not read those rows. The run summary lists each one and why — usually the date or amount column did not map.
Run shows Failed with a reasonThe run stopped on that error. Nothing was silently skipped; fix the cause and press Reconcile now.
Worklist is empty but a statement was importedEvery line matched automatically, or the lines belong to a branch you cannot see. Open the import on 1. Imports to see where its lines went.
A period you did not expectPeriods are opened by imports, never by hand. Its card names the imports and the transaction dates that opened it.
Sign off is greyed outThe button says why beside it: unresolved lines, open exceptions, or no lines filed against that month.