The Africa payments integration guide
From a sandbox key to a live payment, for someone who has never touched a mobile money API.
7 minute read · no sign-up needed
Mobile money is a push, not a charge
The single biggest adjustment coming from card APIs: you do not charge a customer, you ask their phone to approve a payment. Your POST returns almost immediately with a pending payment, and the real answer arrives later, on a webhook, after the payer has entered their PIN on a handset you have no visibility into.
That means two things. Your checkout has to handle a pending state that might last two minutes, and your fulfilment logic belongs in the webhook handler, never in the response to your own POST.
Choosing between the universal checkout and a direct rail
Universal checkout (POST /v0/payments/charge) takes a phone number and works out the network, the country and the gateway behind it. It is the right default: adding a market later becomes a new option in your dropdown rather than a new integration.
The versioned rails (/v1 AzamPay, /v2 Selcom, /v4 MalipoPay, /v5 PawaPay) exist for when you need to target one gateway deliberately, usually because a specific network or a specific settlement arrangement demands it. If you cannot name the reason you are pinning a gateway, do not pin one.
Webhooks: verify, answer fast, then work
Every callback is signed with HMAC-SHA256 over the raw request body. Verify against the raw bytes, not a re-serialised object, or the signature will never match. Compare in constant time.
Answer 200 as soon as you have stored the event. Anything slow, fulfilment, emails, a third-party call, belongs in a queue. A handler that takes eight seconds will be retried while it is still running, and you will process the same payment twice.
- Verify the signature before you parse anything
- Treat delivery as at-least-once and make handlers idempotent on the payment id
- Return 200 for events you do not care about, or you will keep receiving them
Money out is a different discipline
A payout that times out on your side leaves you unable to tell failure from silence. That is why Idempotency-Key is required on every money-out endpoint: send the same key again and you get the original response back rather than a second payout.
Payouts also carry X-Signature-Key, which names the person who authorized them. It is checked against that person’s live access on every call, so removing someone from the team removes their ability to sign, with nothing to remember to revoke.
What verification asks for before you go live
The sandbox needs nothing: sign up, take a uk_test_ key, build the whole integration. Going live needs a verified business and one approved domain, and your live key is tied to that domain.
Start verification early and build in parallel. It is the only part of this that runs on someone else’s clock.
Try it on the sandbox
Test keys work from the first minute and never touch real money, real gateways, or your live webhooks. You can build the whole thing before anyone verifies anything.
