Reconciling mobile money: MTN, Airtel, and ghost transactions
Timeouts, late callbacks, and wrong reference IDs are normal. The ledger should isolate unverified telco money — not hope the teller can unwind it three days later.
TECH247 LIMITED15 Sept 20263 min read
Turning on USSD or app collections from MTN or Airtel sounds like a pipe: member pays, webhook fires, savings go up. In the field the pipe leaks. The telco can debit and your core can stay still. The member can type the wrong reference. The same callback can arrive twice. None of that is exotic. It is the default once volume is real.
The mistake is posting telco money straight into the member’s savings the moment any packet arrives — or, worse, leaving the member to wait while someone walks a paper trail between a float account and a spreadsheet.
Three discrepancies you will get
Telco debited, core did not credit. A timeout, a dropped webhook, a job that died after the HTTP 200. The member has an SMS. Your ledger does not. If you have no holding place for “we heard the telco, we have not accepted it,” the fix is a manual credit and a prayer you do not do it twice.
Wrong reference. The amount is real. It attached to the wrong member, or to no member. Until someone matches it, that money is not “unallocated income.” It is a liability you cannot yet name.
Duplicate callbacks. Idempotency keys exist for a reason. If your listener treats every POST as a new receipt, you will credit twice and spend the afternoon reversing.
These three are enough to design the books. You do not need a longer taxonomy to start.
Hold it until it is theirs
Unverified settlement does not belong in the member’s savings account. It belongs in a suspense (holding) liability until a rule says: this reference, this amount, this telco receipt, this member.
- Matched. Post from suspense to the member’s savings (or loan repayment) with the same identifiers you will show the auditor.
- Unmatched past a threshold. Stay in suspense. Age it. Do not quietly write it to income.
- Duplicate. Second callback finds the first receipt and stops.
That is ordinary accounting. The core has to be able to hold a balance that is not yet a member’s. Fineract can carry those accounts. The integration has to use them instead of treating the webhook as a cash posting.
Match the file to the events
Once a day — or more often if the float is large — the telco’s settlement file and your own event log have to be compared: receipt id, amount, time, reference. Most rows will match. The rest are the work.
We will not claim a percentage. The point of automation is that a person is not re-typing two hundred lines to find three. The point of the leftover pile is that a human still decides the wrong-reference and the genuinely missing callback.
Export formats change. Your matcher should be boring: same keys every day, a list of unmatched on both sides, no silent “force match.”
The front office cannot wait three days
Members who paid from the phone will be at the counter or on WhatsApp the same morning. If the only path is “raise a ticket, wait for IT,” you have not operationalised mobile money. You have added a channel that generates disputes.
What the floor needs is a short, boring workflow: find the receipt, see whether it is in suspense or already on the member, and a checker who can release a matched credit or start a reversal with the telco. That is a workspace job, not a weekend project. The operator UI is where officers already post and check. Whether a given instance has the exact dispute screen you want is something we fit — we do not pretend every deployment ships a magic “ghost transaction” button.
BANKAYO’s work here is the core, the integration, and the queue officers actually use. If you already take MTN or Airtel and the float no longer ties to the ledger, tell us how payments arrive today — files, callbacks, USSD references. We will say what to isolate first.
