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.
| API | Do this |
|---|---|
| MTN MoMo Disbursements | Look 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 Transfers | Never resend automatically. Wait for the transfer.completed webhook, which carries your reference and the id. |
| Paystack Transfers | Look 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
- Save the payout with its own reference before you call the provider, and never change that reference.
- Send a payout only from "new". One that is sending, pending or unknown may already be on its way.
- 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.
- 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".
- 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.