Skip to content

Mobile money payouts

Refunds, seller payouts, salaries: sending money to a wallet. Unlike collecting a payment, nobody approves a payout on their phone, so a duplicate request sends the money twice. Here is what each API offers, and how each one lets you recover from a lost response.

The options

MTN MoMo Disbursements

Pays
MTN MoMo wallets in the markets where MTN offers the MoMo API (for example Uganda and Ghana)
Access
Free sandbox on momodeveloper.mtn.com with a separate Disbursements subscription; production through MTN's partner onboarding in each country
Find it again
By your own reference (X-Reference-Id)
Result
Poll the transfer status, or take the optional callback as a signal to poll
Guides
Node.js · Laravel · Django

M-Pesa B2C (Daraja)

Pays
M-Pesa customers in Kenya
Access
A B2C shortcode from Safaricom, an initiator name and password, and Safaricom's certificate for the SecurityCredential
Find it again
Not by your own reference: results only arrive at your ResultURL
Result
Asynchronous, to the ResultURL; a queue timeout goes to the QueueTimeOutURL and means unknown, not failed
Guides
Node.js

Flutterwave Transfers

Pays
MTN and Airtel Money wallets in Uganda, through one account
Access
A Flutterwave account with transfers enabled and a funded balance
Find it again
Only by Flutterwave's transfer id, which you get from the response or the webhook
Result
The transfer.completed webhook, sent for both success and failure; confirm with GET /v3/transfers/{id}
Guides
Node.js

Paystack Transfers

Pays
MTN, Telecel and AT wallets in Ghana; M-Pesa in Kenya
Access
A registered business on Paystack (starter businesses must upgrade), with a funded balance
Find it again
By your own reference (GET /transfer/verify/:reference)
Result
transfer.success, transfer.failed or transfer.reversed webhooks; confirm with the verify endpoint
Guides
Node.js

Fees are set per contract and aren't published, so ask each provider. Airtel Money disbursements aren't covered yet. Checked November 2026.

When the response is lost

A timeout or a 5xx error doesn't tell you whether the payout was created. What you can safely do next depends on whether the provider lets you look a payout up by your own reference.

APIDo this
MTN MoMo DisbursementsLook it up. A 404 means MTN never received it, so send it again with the same reference; MTN refuses a second one with 409.
M-Pesa B2C (Daraja)Never resend automatically. Wait for the result, or check the M-Pesa statement and settle it by hand.
Flutterwave TransfersNever resend automatically. Wait for the transfer.completed webhook, which carries your reference and the id.
Paystack TransfersLook it up. If Paystack has never seen it, send it again with the same reference, which Paystack treats as the same transfer.

Five rules for safe payouts

  1. Save the payout with its own reference before you call the provider, and never change that reference.
  2. Send a payout only from "new". One that is sending, pending or unknown may already be on its way.
  3. After a timeout, look it up before doing anything else. Resend only when the provider can say it never received it, and then with the same reference.
  4. Settle only from the provider's own answer, never from a webhook body. A different amount or currency goes to a person, not to "paid".
  5. Protect whatever creates payouts: an authenticated admin, limits per payout and per day, and a record of who asked.

What trips people up

  • MTN MoMo Disbursements: Disbursements needs its own subscription key, API user and key: the Collections ones don't work. The sandbox only accepts EUR.
  • M-Pesa B2C (Daraja): ResponseCode 0 is only an acknowledgement. The SecurityCredential must be made with the sandbox or production certificate to match the environment. Amounts are whole shillings.
  • Flutterwave Transfers: "Transfer Queued Successfully" is not a completed payout. Confirm the operator code for each network with one small live payout.
  • Paystack Transfers: Transfers need an OTP until you turn that off in the dashboard. A successful transfer can still be reversed later. Amounts are in subunits.

Skip the plumbing

The starter kits for Laravel, Django and Next.js send payouts with all four APIs, using the rules on this page: sent once, settled from the provider's answer, with limits and an audit trail. Or have it built for you.

Frequently asked questions

Which API should I use to send money to mobile money wallets?

It depends on the country and what you can sign up for. In Kenya, M-Pesa B2C is the direct route; Paystack also pays M-Pesa numbers. In Ghana, Paystack reaches MTN, Telecel and AT. In Uganda, Flutterwave pays MTN and Airtel through one account, and MTN MoMo Disbursements pays MTN directly.

My payout request timed out. Should I send it again?

Not blindly. With MTN MoMo and Paystack you can look the payout up by your own reference first, and resend with the same reference only if it was never received. With M-Pesa B2C and Flutterwave you can't look it up by your reference, so wait for the result or webhook instead, or check the statement.

Is a "success" response a completed payout?

Usually not. MoMo answers 202 Accepted, M-Pesa B2C answers ResponseCode 0, Flutterwave answers "Transfer Queued Successfully" and Paystack answers "Transfer has been queued". Each means the request was accepted. The result comes later, from a status lookup, a webhook or the B2C ResultURL.

Can I send mobile money payouts without a registered business?

Paystack only allows transfers for registered businesses. M-Pesa B2C needs a B2C shortcode from Safaricom, and MTN gives production Disbursements access through its partner onboarding, both of which are business processes. Check each provider's current requirements before you plan around one.

Collecting payments instead? See mobile money by country.

Need this built for you?

I integrate MTN MoMo, M-Pesa and Airtel Money into Laravel / PHP, Python / Django, Node.js / Next.js apps: payments, callbacks, payouts and go-live, with tests.