Developers

Mobile money API for Africa: a guide to integrating payments

Collecting and paying out with mobile money works differently from card payments. Here is how a solid integration works, from the first request to going live.

By Monerails TeamPublished 6 min read

If you have integrated card payments before, mobile money will surprise you. There is no card number to capture and no form to submit. The customer confirms the payment on their own phone, in their operator’s menu, sometimes seconds later, sometimes minutes later. Your integration has to live with that delay, and with the fact that each country has its own operators, currency and habits.

This guide walks through what a production-grade mobile money integration looks like: the flows, the statuses, the edge cases and the safeguards. The examples use the Monerails API, but the principles apply to any provider you consider.

How mobile money differs from cards

Three differences shape everything else.

  • The customer approves on their phone. You send a payment request; the operator contacts the customer, who approves with their PIN. Some operators send a one-time code by SMS instead, which the customer has to give back to you.
  • The answer is asynchronous. When you create a payment, you rarely know the outcome straight away. The operator answers when the customer acts, or when the request expires.
  • Every market is local. Operators, currencies, number formats and limits change from one country to the next. Even two countries that share a currency, such as Cameroon and Gabon with the CFA franc (XAF), do not share operators.

A good integration accepts these constraints instead of fighting them: it shows the customer clear instructions, waits for a webhook, and never assumes that silence means failure.

Collections, payouts and settlements

A mobile money API usually covers three movements of money.

  • Collections: a customer pays you from their wallet. The net amount, after the fee, lands on your balance.
  • Payouts: you send money from your balance to someone else’s wallet or bank account: a supplier, a rider, an employee, a customer you refund.
  • Settlements: you move your own balance to your own bank or mobile money account.

With Monerails, the three use the same balances and the same ledger, so every movement can be reconciled from a single list of transactions. Monerails covers 22 African countries; in Nigeria, only payouts are open for now, collections are coming. The full list is on the coverage page.

A collection, step by step

Here is a collection request in Cameroon. Amounts are integers in the minor unit of the currency; for the CFA franc, that is the franc itself.

curl https://api.monerails.com/v1/collections \
  -H "Authorization: Bearer $MONERAIL_KEY" \
  -H "Idempotency-Key: order-42" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "currency": "XAF",
    "phone": "237670000999",
    "operator": "MTN",
    "customer": { "name": "Awa Ndiaye" },
    "client_reference": "order_42"
  }'

The collection then moves through a small set of statuses:

StatusWhat it means
pendingThe request reached the operator; the customer has to approve it on their phone.
requires_otpThe customer received a one-time code; send it back with authorize.
processingThe operator is processing the payment.
succeededPaid. The fee and the net amount are known, and the net is on your balance.
failed / expiredNot paid, or not approved in time. The collection can simply be created again.

The response also carries a next_action field that tells your app what to display: either a message asking the customer to approve on their phone, with a USSD code to dial if the prompt does not appear, or a request for the code they received. Show it as is. Customers who see clear instructions abandon far less often.

Operators, currencies and phone numbers

Three details cause most integration bugs.

Shared currencies

The XAF franc is used across Central Africa and the XOF franc across eight West African countries. A currency alone therefore does not tell you the country. Send the country field whenever you collect or pay out outside the default country of the currency (Cameroon for XAF, Senegal for XOF).

Operator codes

Rather than hard-coding a list of operators per country, call GET /v1/operators for a currency, and a country where needed, and fill your operator picker from the answer. Cache it for a few minutes. New operators then appear in your checkout without a code change.

Phone numbers

Accept numbers the way your customers type them, then normalise. The API accepts international formats (237670000999, +237 670 00 09 99) and local numbers when the country is known. Validate the length in your form so typos are caught before the request leaves.

Webhooks, not polling

Because the outcome arrives later, your server should be told when it happens. Register an HTTPS endpoint and subscribe to the events you need: collection.succeeded, collection.failed, collection.expired, payout.succeeded, payout.failed and so on.

Three rules make a webhook handler reliable:

  1. Verify the signature. Monerails signs each event with HMAC-SHA256 in the Monerail-Signature header. Compute the signature on the raw body, compare in constant time and refuse timestamps that are more than five minutes old.
  2. Answer fast, then process. Return a 2xx response first and do the heavy work afterwards. Slow handlers get retried.
  3. Deduplicate by event ID. The same event can arrive more than once. Process each id once.

Deliver the goods when you receive collection.succeeded, never when the customer comes back to your site. The webhooks guide has verification code in eight languages.

Idempotency: never pay twice

A network error does not tell you whether your request reached the server. For a collection, a blind retry is annoying: the customer receives two prompts. For a payout, it is expensive: you may pay twice.

The fix is an Idempotency-Key header on every request that moves money. Use a value tied to the operation in your own system, such as payout-inv-118. If the same request is sent again with the same key, the API returns the original response instead of running it again. Two more habits complete the picture:

  • A 202 response means the operation is on its way. Do not create it again; wait for the webhook or retrieve the object.
  • Retry network errors and 5xx responses with the same key and some backoff. Do not retry 4xx errors before fixing the request.

Fees and reconciliation

On a collection, the fee is deducted from what you receive: the object carries amount, fee and net once it succeeds. On a payout, the fee is added on top: the recipient gets the full amount. Failed operations are not charged; for a payout, the amount and the fee return to your balance.

Put your order or invoice number in client_reference and any extra context in metadata. Both come back on the object and in every event, which makes matching trivial. Reconcile once a day from the list of transactions, and use the list of events to catch up after any downtime. Prices per country are on the pricing page, with no monthly subscription.

From test mode to live

Build everything in test mode first. Test keys start with mr_test_ and only see test data; live keys start with mr_live_. Test mode is a separate copy of your account with its own balances, events and webhook endpoints, and it never moves real money.

Before switching to live, run through this checklist:

  • Your business verification is approved, and your live key has only the role your integration needs.
  • Live webhook endpoints are created, with their own secrets, and every signature is verified.
  • Every POST that moves money carries an Idempotency-Key.
  • Every final status is handled: failed and expired collections, failed payouts.
  • Your tests check "livemode": false, so a misplaced key fails your tests instead of moving money.

When you do not need the API

Not every business needs an integration. If you sell through WhatsApp, social media or invoices, a payment link gives you the same collection, with the same fees and webhooks, without writing code. And if you mainly need to pay people, read our guide to bulk payouts.

When you are ready to build, the quickstart takes you from your first API call to your first test webhook in a few minutes.

Ready to collect, pay out and issue cards?

Open an account in minutes. The sandbox is free, with no subscription.

Developers

Mobile money API for Africa: a guide to integrating payments

Collecting and paying out with mobile money works differently from card payments. Here is how a solid integration works, from the first request to going live.

By Monerails TeamPublished 6 min read

If you have integrated card payments before, mobile money will surprise you. There is no card number to capture and no form to submit. The customer confirms the payment on their own phone, in their operator’s menu, sometimes seconds later, sometimes minutes later. Your integration has to live with that delay, and with the fact that each country has its own operators, currency and habits.

This guide walks through what a production-grade mobile money integration looks like: the flows, the statuses, the edge cases and the safeguards. The examples use the Monerails API, but the principles apply to any provider you consider.

How mobile money differs from cards

Three differences shape everything else.

  • The customer approves on their phone. You send a payment request; the operator contacts the customer, who approves with their PIN. Some operators send a one-time code by SMS instead, which the customer has to give back to you.
  • The answer is asynchronous. When you create a payment, you rarely know the outcome straight away. The operator answers when the customer acts, or when the request expires.
  • Every market is local. Operators, currencies, number formats and limits change from one country to the next. Even two countries that share a currency, such as Cameroon and Gabon with the CFA franc (XAF), do not share operators.

A good integration accepts these constraints instead of fighting them: it shows the customer clear instructions, waits for a webhook, and never assumes that silence means failure.

Collections, payouts and settlements

A mobile money API usually covers three movements of money.

  • Collections: a customer pays you from their wallet. The net amount, after the fee, lands on your balance.
  • Payouts: you send money from your balance to someone else’s wallet or bank account: a supplier, a rider, an employee, a customer you refund.
  • Settlements: you move your own balance to your own bank or mobile money account.

With Monerails, the three use the same balances and the same ledger, so every movement can be reconciled from a single list of transactions. Monerails covers 22 African countries; in Nigeria, only payouts are open for now, collections are coming. The full list is on the coverage page.

A collection, step by step

Here is a collection request in Cameroon. Amounts are integers in the minor unit of the currency; for the CFA franc, that is the franc itself.

curl https://api.monerails.com/v1/collections \
  -H "Authorization: Bearer $MONERAIL_KEY" \
  -H "Idempotency-Key: order-42" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 5000,
    "currency": "XAF",
    "phone": "237670000999",
    "operator": "MTN",
    "customer": { "name": "Awa Ndiaye" },
    "client_reference": "order_42"
  }'

The collection then moves through a small set of statuses:

StatusWhat it means
pendingThe request reached the operator; the customer has to approve it on their phone.
requires_otpThe customer received a one-time code; send it back with authorize.
processingThe operator is processing the payment.
succeededPaid. The fee and the net amount are known, and the net is on your balance.
failed / expiredNot paid, or not approved in time. The collection can simply be created again.

The response also carries a next_action field that tells your app what to display: either a message asking the customer to approve on their phone, with a USSD code to dial if the prompt does not appear, or a request for the code they received. Show it as is. Customers who see clear instructions abandon far less often.

Operators, currencies and phone numbers

Three details cause most integration bugs.

Shared currencies

The XAF franc is used across Central Africa and the XOF franc across eight West African countries. A currency alone therefore does not tell you the country. Send the country field whenever you collect or pay out outside the default country of the currency (Cameroon for XAF, Senegal for XOF).

Operator codes

Rather than hard-coding a list of operators per country, call GET /v1/operators for a currency, and a country where needed, and fill your operator picker from the answer. Cache it for a few minutes. New operators then appear in your checkout without a code change.

Phone numbers

Accept numbers the way your customers type them, then normalise. The API accepts international formats (237670000999, +237 670 00 09 99) and local numbers when the country is known. Validate the length in your form so typos are caught before the request leaves.

Webhooks, not polling

Because the outcome arrives later, your server should be told when it happens. Register an HTTPS endpoint and subscribe to the events you need: collection.succeeded, collection.failed, collection.expired, payout.succeeded, payout.failed and so on.

Three rules make a webhook handler reliable:

  1. Verify the signature. Monerails signs each event with HMAC-SHA256 in the Monerail-Signature header. Compute the signature on the raw body, compare in constant time and refuse timestamps that are more than five minutes old.
  2. Answer fast, then process. Return a 2xx response first and do the heavy work afterwards. Slow handlers get retried.
  3. Deduplicate by event ID. The same event can arrive more than once. Process each id once.

Deliver the goods when you receive collection.succeeded, never when the customer comes back to your site. The webhooks guide has verification code in eight languages.

Idempotency: never pay twice

A network error does not tell you whether your request reached the server. For a collection, a blind retry is annoying: the customer receives two prompts. For a payout, it is expensive: you may pay twice.

The fix is an Idempotency-Key header on every request that moves money. Use a value tied to the operation in your own system, such as payout-inv-118. If the same request is sent again with the same key, the API returns the original response instead of running it again. Two more habits complete the picture:

  • A 202 response means the operation is on its way. Do not create it again; wait for the webhook or retrieve the object.
  • Retry network errors and 5xx responses with the same key and some backoff. Do not retry 4xx errors before fixing the request.

Fees and reconciliation

On a collection, the fee is deducted from what you receive: the object carries amount, fee and net once it succeeds. On a payout, the fee is added on top: the recipient gets the full amount. Failed operations are not charged; for a payout, the amount and the fee return to your balance.

Put your order or invoice number in client_reference and any extra context in metadata. Both come back on the object and in every event, which makes matching trivial. Reconcile once a day from the list of transactions, and use the list of events to catch up after any downtime. Prices per country are on the pricing page, with no monthly subscription.

From test mode to live

Build everything in test mode first. Test keys start with mr_test_ and only see test data; live keys start with mr_live_. Test mode is a separate copy of your account with its own balances, events and webhook endpoints, and it never moves real money.

Before switching to live, run through this checklist:

  • Your business verification is approved, and your live key has only the role your integration needs.
  • Live webhook endpoints are created, with their own secrets, and every signature is verified.
  • Every POST that moves money carries an Idempotency-Key.
  • Every final status is handled: failed and expired collections, failed payouts.
  • Your tests check "livemode": false, so a misplaced key fails your tests instead of moving money.

When you do not need the API

Not every business needs an integration. If you sell through WhatsApp, social media or invoices, a payment link gives you the same collection, with the same fees and webhooks, without writing code. And if you mainly need to pay people, read our guide to bulk payouts.

When you are ready to build, the quickstart takes you from your first API call to your first test webhook in a few minutes.

Ready to collect, pay out and issue cards?

Open an account in minutes. The sandbox is free, with no subscription.