PesaBridge
Yonke imininingwane
EngineeringJun 2026 · 11 imizuzu yokufunda

Why your wallet's ledger must be double-entry

Single-table balances drift. A clearing-grade ledger can't. Here's the invariant that lets a regulator trust your books.

T
The PesaBridge team · Engineering

The fastest way to ship a wallet is a single number in a row: a balance column you increment when money arrives and decrement when it leaves. It works in the demo. It works in the pilot. And then, somewhere around the first ten thousand real transactions, somebody runs a report, the numbers do not tie out, and nobody can say where the money went. Once real value is involved, that is not a bug — it is the end of trust, and trust is the only thing a financial product actually sells.

The discipline that prevents this is six hundred years old. Double-entry bookkeeping is not an accounting nicety bolted onto software; it is the data model that makes a money system provable. This is why every clearing-grade ledger is built on it, and why your wallet's must be too.

The invariant that everything else hangs on

In a double-entry ledger, money is never created or destroyed — it only moves. Every transaction posts a set of entries whose debits and credits sum to exactly zero. A deposit debits an agent's float account and credits a customer's wallet by the same amount. A send debits the sender, credits the recipient, and credits a revenue account for the fee. Across the entire system, at every instant:

The sum of all debits always equals the sum of all credits. The books net to zero. Always.

That single property is doing enormous work. It means there is no code path that can create or destroy value — a balance can only change as the mirror of another balance changing. It means a CFO can sign the books and a regulator can trust them, because the system is correct by construction, not by hope. And it means reconciliation stops being a forensic investigation and becomes a property you can assert and test.

Contrast that with the single-balance approach. There, a balance is a fact with no provenance. If it is wrong, you cannot tell whether a credit was applied twice, a debit was lost, or a race condition interleaved two writes. You have a number, but no story. A double-entry ledger is, above all, a system that keeps the story.

Accounts, entries, and why you model money as movement

The mental shift is to stop thinking of a balance as a thing you store and start thinking of it as a thing you derive. A balance is the sum of the entries posted against an account. Wallets are accounts. Agent floats are accounts. Fee revenue, suspense, settlement, unclaimed funds — all accounts. Every movement of value is a transaction containing two or more entries that net to zero.

This has a profound consequence: the ledger is append-only. You never update a balance in place; you post new entries. History is never rewritten. That is what makes the system auditable — every shilling that ever moved left a permanent, ordered trail, and any balance at any past moment can be reconstructed by replaying the entries up to that point.

The parts people forget — and pay for later

The invariant is necessary but not sufficient. Three properties turn a correct-on-paper ledger into one that survives contact with real networks and real adversaries.

Idempotency

Networks drop. A client sends a request, the response is lost in transit, and the client — correctly — retries. Without protection, the second request posts a second transaction and the customer is charged twice. The fix is an idempotency key: each logical operation carries a unique token, the ledger records which tokens it has already applied, and a retried request with a seen token returns the original result instead of posting again.

This sounds simple and is subtle. The check and the write must be atomic — if two retries arrive concurrently, exactly one must win and the other must observe the result, with no window in which both proceed. Get this wrong and you have built a double-spend under load, which is precisely the failure that does not appear in testing and does appear on launch day.

Reversals, not deletions

Things go wrong: a dispute, an error, a fraud claim. The instinct is to delete the offending transaction. Never delete. A deleted transaction is a hole in the audit trail, and a regulator reads a hole as a cover-up. Instead you post a reversal — a new transaction whose entries are the mirror of the original, leaving the original intact. The net effect on balances is zero; the history shows both the mistake and its correction, with a reason and an operator attached. A single reversal engine that can unwind any department — P2P, Pay Bill, Buy Goods, agent cash, float, savings — is the difference between an auditable system and an argument.

Pre-flight checks

Balance, limits and KYC status are verified before a transaction posts, inside the same atomic operation that posts it — never after, and never in the app where they can be bypassed. The limit a customer cannot exceed is not a disabled button; it is a constraint enforced at the point money would move. The app is a convenience; the ledger is the authority. Any rule that matters must live where the money lives.

Concurrency: where correct designs go to die

Two requests hit the same wallet at the same instant — a send and a withdrawal, each individually valid, together exceeding the balance. A naive implementation reads the balance in both, finds it sufficient in both, and posts both, overdrawing the account. This is the canonical financial race condition, and it is invisible until you are under real concurrency.

The defence is to make the account the unit of serialization: posting against an account takes a lock or uses an atomic conditional write, so the two requests are ordered, the second sees the first's effect, and exactly one succeeds. The hard part is doing this without serializing the entire ledger — locking per account, not globally, so the system stays fast while staying correct. This is the kind of problem that separates engineers who have built ledgers from engineers who are about to learn why they are hard.

Reconciliation becomes a property, not a fire drill

In a single-balance system, reconciliation is a nightly investigation: export everything, compare against the rails, hunt for the discrepancy, hope you find it before morning. In a double-entry system, reconciliation is mostly a tautology you can assert: internal accounts net to zero by construction, and the remaining work is matching external settlement — what the bank, the mobile money operator and the card processor actually moved — against the suspense and settlement accounts the ledger already tracks. Discrepancies surface as specific unmatched entries with a clear question attached, not as a missing number with no story.

A worked example: one send, posted

Consider a customer sending 1,000 to a friend, with a fee of 25. In a double-entry ledger, that single send is one transaction with three entries. Amounts are illustrative.

AccountDebitCredit
Sender's wallet1,025
Recipient's wallet1,000
Fee revenue25
Total1,0251,025

Debits equal credits, so the transaction nets to zero. Every question you might ask about it has an answer in the entries: how much the sender paid, how much the recipient received, how much the operator earned, and when. If the send is later reversed, a mirror transaction posts the same three lines the other way, and both remain in the history.

Cash at an agent, posted

Agent transactions show why the model scales. When a customer deposits 2,000 in cash at an agent, the agent's e-float moves to the customer, and the agent earns a commission. With an illustrative commission of 20:

AccountDebitCredit
Agent float2,000
Customer wallet2,000
Commission expense20
Agent commission payable20
Total2,0202,020

The cash itself never touches the ledger, because it changed hands physically at the counter. What the ledger records is the electronic value moving from the agent to the customer, and the obligation to pay the agent for the service. When commissions are paid out, another balanced transaction clears the payable. Nothing is estimated, nothing is calculated later in a spreadsheet.

Designing the chart of accounts

The accounts you create decide what questions the ledger can answer. A wallet platform typically needs:

  • Customer, agent, merchant and corporate wallets, one account each;
  • Fee revenue accounts split by product, so you can see what each service earns;
  • Commission expense and commission payable for agents and partners;
  • Settlement accounts per external partner: each bank, switch, biller and rail;
  • Suspense accounts for money whose final destination is not yet confirmed, such as a transfer awaiting a partner's response;
  • A safeguarding mirror that tracks what should sit in the trust account, so the daily comparison with the bank statement is a single check.

A useful test: if finance asks a question and the answer requires a spreadsheet, you are probably missing an account.

Testing a ledger properly

Ordinary unit tests are not enough for a system that must never be wrong. The tests that matter check properties rather than examples:

  • Every transaction nets to zero, generated across thousands of random transaction types and amounts.
  • Replay equals state: rebuilding every balance from the entries gives exactly the balances stored.
  • Retries never duplicate: sending the same request many times, including concurrently, posts once.
  • Races never overdraw: firing competing debits at one account never takes it below its limit.
  • Reversals restore: reversing any transaction returns every affected balance to its previous value.

These tests belong in the release pipeline, and they should also run against production data in read-only form, so that the invariant is continuously checked on the real books, not just on test data.

Moving from a single-balance system

Many teams discover these principles after launch. Migration is possible without stopping the service:

  1. Freeze the schema of the existing balances and take a snapshot at a cut-off time.
  2. Create opening-balance transactions in the new ledger that post each snapshot balance against an equity or migration account.
  3. Run both systems in parallel for a period, posting every new transaction to both and comparing balances daily.
  4. Investigate every difference until the parallel run is clean for several consecutive days.
  5. Switch reads to the new ledger, then retire the old balance column.

The parallel run usually uncovers historical errors in the old system. Budget time to resolve them honestly rather than forcing the new ledger to match a wrong number.

The payoff

Build the ledger this way and a series of hard things become easy. Statements are a query over entries. Audits are a replay. Disputes have a paper trail. New channels — a USSD send, an app send, an API charge — are just different front doors onto the same guaranteed core, which means they cannot drift from one another. The rules live in one place, enforced once, for everyone.

Build it the fast way and you inherit the opposite: a number you cannot explain, a reconciliation you cannot trust, and a launch day on which you discover that the cheapest thing to build was also the most expensive thing to own.

More than one currency

Wallets that handle remittances or regional payments meet a second dimension: currency. The rule stays the same, but it applies per currency. Each account holds one currency, and each transaction must net to zero in every currency it touches. A conversion is therefore two balanced legs joined by an exchange account: the customer's shillings move to the operator's shilling position, and dollars move from the operator's dollar position to the recipient. The exchange accounts show the operator's currency exposure at any moment, which is exactly what treasury needs to manage risk. Mixing currencies in one account, or converting on the fly without recording both legs, is how multi-currency ledgers lose money quietly.

Statements, month-end and audit

Because balances are derived from entries, statements are simply the entries for an account over a period, with an opening and closing balance that must agree with the entries between them. Month-end becomes a check rather than a project: trial balance nets to zero, safeguarding matches the bank, suspense is explained, and fee and commission totals agree with the product reports. Auditors can pick any balance and trace it back to individual transactions, each with a timestamp, a channel, a reference and, where a person was involved, an operator. That traceability is what makes a first audit routine instead of frightening.

Suspense accounts, handled well

Suspense is where money waits when its final destination is not yet known: a transfer to another network awaiting confirmation, a deposit whose reference does not match a customer, a partner payment that arrived without details. Suspense is useful because it keeps the books balanced while a question is open. It becomes dangerous when it turns into a place where money is forgotten. Good practice is simple: every item in suspense carries its reference and the reason it is there, every item has an owner and an age, the balance is reviewed daily, and anything older than a few days is escalated. A suspense balance that only ever grows is a sign that something upstream is broken.

Frequently asked questions

Is double-entry slower than updating a balance?

Posting two or three entries instead of one update adds little cost, and balances can be maintained alongside the entries for fast reads. The real performance work is in locking per account rather than globally, which a well-designed ledger does.

Do we still need accountants if the ledger is double-entry?

Yes. The ledger guarantees internal consistency; people still decide how products are accounted for, investigate external breaks and report to regulators. What changes is that they spend their time on judgement rather than on finding missing numbers.

Can a ledger entry ever be edited?

No. Corrections are new transactions, usually reversals, with a reason and an operator recorded. An editable ledger cannot be audited.

PesaBridge enforces the invariant in the core. The apps, USSD and developer API don't re-implement money movement — they present rules the ledger already guarantees, so every channel is correct because the centre is correct.

Izikhwama ezigcina inani Inethiwekhi yabameli Izinkokhelo zomthengisi USSD I-API yabathuthukisi Isicelo-sokukhokha Amazinga e-KYC Ukubuyisela Ukusabalalisa i-float Ukubhadala Ama-webhook asayiniwe White-label Izikhwama ezigcina inani Inethiwekhi yabameli Izinkokhelo zomthengisi USSD I-API yabathuthukisi Isicelo-sokukhokha Amazinga e-KYC Ukubuyisela Ukusabalalisa i-float Ukubhadala Ama-webhook asayiniwe White-label

Usukulungele ukwethula isikhwama sakho?

Bhuka idemo bese sisungula ibhrendi, izwe nama-rail akho — futhi sikuhambise kuwo ama-app, i-admin ne-API.

Ungathanda ukukhuluma? Shayela +254 746 883809