A single-country payment integration can survive on a charge endpoint and a webhook. A multi-country one cannot. Somewhere between the first market and the third, a business discovers it needs infrastructure: the boring layers between "the customer paid" and "the books are right".
The scale is real. The GSMA counted around 1.2 billion registered mobile money accounts in Africa in 2025 1, spread across dozens of providers and markets. If you want to serve a meaningful slice of them, these are the six layers you need, in the order you will need them. Some Ultraner provides. Some only you can build, because they encode your business.
Layer 1: Routing
The job: given a payer, pick the right rail.
In one country, routing is a constant. Across many, it is a function of the payer's country, their network, the currency, and which rails are healthy right now. Tanzania alone has several gateways serving the same networks. Most other markets are served by one rail, with others coming.
What Ultraner does: the universal charge endpoint reads the country off the phone prefix and routes to whichever gateway serves that network there, with automatic fallback in Tanzania and Rwanda if one gateway declines outright. A fallback never fires after a prompt may already have been sent, so the payer is never prompted twice. You choose the network. Ultraner chooses the gateway.
What you still own: the decision of which methods to show, in which order, to which customer. That is product, not plumbing. See how it works for the routing model.
Layer 2: A ledger you trust more than any dashboard
The job: know, at any moment, who owes whom how much, in which currency.
This is the layer teams skip and regret. A payment provider's dashboard tells you what the provider did. Your ledger tells you what it meant for your business: which order it paid, which seller it is owed to, which fee you bear, which refund reversed it.
The rules are old and they still work:
- Store money as integers in the currency's minor unit, with the currency code beside every amount. ISO 4217 defines the exponents 4; remember that several African currencies, including the CFA francs, have none.
- Never update a balance in place. Record entries, and derive balances from them.
- Every entry references the external transaction that caused it. For Ultraner, that is the
transaction_id. - Keep currencies separate until a real conversion happens. A KES balance and a TZS balance are not one number.
Layer 3: Events you can trust
The job: learn about every state change, reliably, and act on it once.
Mobile money is asynchronous by nature. The customer approves on their phone, seconds or minutes after your request. So your system runs on webhooks, and webhooks need three properties:
- Authenticity. Ultraner signs every event with HMAC-SHA256 3. Verify the
x-ultraner-signatureheader before you trust a byte of the body. The webhooks capability links the check. - Idempotent handling. You will occasionally receive the same event twice. Key your processing on the transaction, and make the second delivery a no-op.
- Order tolerance. A
payment.failedfor an unanswered prompt can be followed by apayment.successif the network confirms late. Let a later success overrule an earlier failure for the same transaction.
The full list of events Ultraner sends is short on purpose: payment, recurring, cross-border, disbursement, refund and bill-pay outcomes.
Layer 4: Safe retries on money out
The job: make "did that payout go through?" a question with a safe answer.
When a request to move money out times out, you cannot know whether it succeeded. Retrying blindly can pay twice. Not retrying can pay nobody. The answer is an idempotency key, the pattern the IETF has been standardising for HTTP APIs 2.
Ultraner requires an Idempotency-Key on every API-key call that moves money out. Same key and same body returns the original response. Same key and a different body is refused as a conflict, because that is a bug, not a retry. A failed attempt frees the key so a real retry works.
Your part: generate keys deterministically from the business obligation (payout-seller-221-2026-10-03), not randomly per attempt.
Layer 5: Settlement and payouts, kept apart
The job: get money to where it is useful.
Two different things hide under "payouts", and your infrastructure must model them separately:
- Settlement moves your balance to your own bank account. It works to any country. The quote shows what leaves your balance, the FX rate if currencies differ, the fees and the expected arrival date, and the settlement moves through
pending,in_transit,paid, orfailedwith everything returned. - Disbursement pays into somebody else's mobile money wallet. It is live per market, per rail. Tanzania is live today, others follow.
If your product pays sellers, drivers or staff, your infrastructure needs a path for both: wallet payouts where live, and a settle-then-pay-onward path elsewhere. Settlement and disbursement each have a page with the current scope.
Layer 6: Reconciliation
The job: prove every day that your ledger, your provider and your bank agree.
Reconciliation is where multi-country operations either stay calm or slowly drift into chaos. A daily job should:
- Pull every transaction from Ultraner for the day, per currency.
- Match each one to a ledger entry by
transaction_id. - Flag anything on one side and not the other.
- Match settlements to bank statement lines.
- Produce one exceptions list a person reviews.
Exceptions are normal. Late confirmations, refunds that crossed a day boundary, a fee that changed. Unreviewed exceptions are not.
What to build first
If you are about to add your second country, here is the order that hurts least:
- Ledger with currency on every amount.
- Signed, idempotent webhook handler.
- Per-market data table (currency, decimals, phone format, networks).
- Idempotency keys on every money-out call.
- Daily reconciliation with an exceptions list.
- Settlement and payout paths per market.
Routing comes free with the integration. Everything else is yours. Start with the docs for the request shapes, and test the whole chain in the sandbox before a single real shilling or franc moves.




