There’s no single best mobile money API. It depends on your country, which wallets your customers use, and whether you can get approved. This page compares the three most common routes, MTN’s own MoMo API, Flutterwave and Paystack, on the things that change your code and your launch date.
Everything here comes from each provider’s public documentation and from the tested guides on this site, checked in November 2026. Fees aren’t compared, because none of the three publishes one price for every market.
At a glance
| MTN MoMo Open API | Flutterwave (v3) | Paystack | |
|---|---|---|---|
| What it is | MTN’s own API | Aggregator | Aggregator |
| Mobile money markets | MTN markets, chosen by target environment (for example mtnuganda, mtnghana) |
Several African markets; this site covers Uganda (MTN and Airtel) | Ghana, Kenya, Côte d’Ivoire |
| Wallets | MTN MoMo only | Several networks per country | Ghana: MTN, Telecel, AT · Kenya: M-Pesa, Airtel |
| Cards too | No | Yes | Yes |
| Test environment | Sandbox (EUR only, test numbers simulate outcomes) | Test mode (Uganda mobile money approves automatically) | Test mode with test numbers |
| Customer approves | PIN prompt on the phone | On a Flutterwave page you redirect to | Prompt on the phone (pay_offline) |
| Webhook security | Not signed | verif-hash header = your secret hash |
HMAC SHA512 signature |
| Confirm a payment | GET …/requesttopay/{X-Reference-Id} |
GET /v3/transactions/{id}/verify or verify_by_reference |
GET /transaction/verify/:reference |
| Payouts | Disbursements, a separate product | Transfers | Transfers |
Choose by country
- Uganda: MTN MoMo and Airtel Money directly, or Flutterwave for both in one integration. Paystack’s mobile money isn’t available. See mobile money in Uganda.
- Kenya: most payments are M-Pesa. Safaricom’s Daraja API is the direct route; Paystack takes M-Pesa and Airtel Money through one account. MTN doesn’t operate in Kenya. See mobile money in Kenya.
- Ghana: Paystack reaches MTN, Telecel and AT through one account; MTN’s API reaches MTN directly. See mobile money in Ghana.
Direct API or aggregator?
Go direct (MTN MoMo, Daraja, Airtel) when you want no third party between you and the network, you mostly serve one network, or your volume justifies negotiating with the network. The cost is one integration and one approval per network.
Use an aggregator (Flutterwave, Paystack) when you need several networks, or cards, quickly. You integrate once, get approved once, and see every payment in one dashboard. The cost is a dependency on the aggregator’s approval and coverage.
A common path is to start with an aggregator, then add a direct integration for your biggest network once volume grows. Build behind one interface from the start and that switch is a configuration change, not a rewrite. The starter kits are built that way.
What changes in your code
Who knows the reference first. All three let you choose the ID before you call them: MTN’s X-Reference-Id, Flutterwave’s tx_ref, Paystack’s reference. Save it with the order before the call, so a timeout never loses a payment.
How the customer approves. MTN and Paystack push a prompt to the phone; your page waits and polls your own server. Flutterwave’s Uganda charge returns a page to redirect the customer to, and they come back to your site afterwards. Plan your checkout page for that.
How you trust a webhook.
- Paystack signs the raw body. Check
x-paystack-signaturebefore parsing the JSON. - Flutterwave v3 sends your chosen secret hash in
verif-hash. Compare it in constant time. - MTN MoMo callbacks carry no signature. Put your reference in the callback URL and confirm with the status endpoint.
With all three, the webhook only tells you something happened. Confirm with the provider’s status or verify endpoint before giving value, and check the amount and currency.
The three confirm calls, side by side (Node.js 18+, secrets from environment variables):
// confirm.js: ask each provider for the real status before giving value
const env = process.env;
// MTN MoMo: X-Reference-Id you saved; token from POST /collection/token/
export async function mtnStatus(referenceId, token) {
const res = await fetch(`${env.MOMO_BASE_URL}/collection/v1_0/requesttopay/${referenceId}`, {
headers: {
Authorization: `Bearer ${token}`,
'X-Target-Environment': env.MOMO_TARGET_ENV,
'Ocp-Apim-Subscription-Key': env.MOMO_SUBSCRIPTION_KEY,
},
});
return res.json(); // status: SUCCESSFUL | FAILED | PENDING
}
// Flutterwave v3: your tx_ref
export async function flutterwaveStatus(txRef) {
const res = await fetch(`https://api.flutterwave.com/v3/transactions/verify_by_reference?tx_ref=${encodeURIComponent(txRef)}`, {
headers: { Authorization: `Bearer ${env.FLW_SECRET_KEY}` },
});
return (await res.json()).data; // status: successful | failed | pending
}
// Paystack: your reference
export async function paystackStatus(reference) {
const res = await fetch(`https://api.paystack.co/transaction/verify/${encodeURIComponent(reference)}`, {
headers: { Authorization: `Bearer ${env.PAYSTACK_SECRET_KEY}` },
});
return (await res.json()).data; // status: success | failed | abandoned | ongoing | ...
}
Then check the amount and currency against your order, and mark it paid only if it is still unpaid. The full guides below add timeouts, retries and tests.
When there’s no webhook. Paystack says its webhooks currently fire only for successful transactions; MTN callbacks and Flutterwave webhooks can also be late or missing. Every integration needs a scheduled job that checks pending payments.
Amounts. Paystack wants the currency’s subunit (GHS 25.50 is 2550). MTN wants the amount as a string ("500"). Flutterwave takes a number in the main unit.
The tested guides
| Provider | Guides |
|---|---|
| MTN MoMo | Node.js · Laravel · Django · Payouts |
| Flutterwave | Node.js · Laravel · Django · Payouts |
| Paystack | Node.js · Laravel · Django |
| M-Pesa (Daraja) | Node.js · Laravel · Django · B2C payouts |
| Airtel Money | Node.js · Laravel |
Each one was run in a fresh project, with tests for timeouts, duplicate and forged webhooks, and payments whose webhook never arrived.
Frequently asked questions
Is it better to integrate MTN MoMo directly or use Flutterwave?
Directly if you want no aggregator between you and MTN and are willing to integrate each network separately. Flutterwave if you want several networks, and cards, through one integration and one approval. Many businesses start with an aggregator and add direct integrations as volume grows.
Does Paystack work in Uganda?
Not for mobile money. Paystack documents mobile money for businesses in Ghana, Kenya and Côte d'Ivoire. In Uganda, use MTN MoMo and Airtel Money directly, or Flutterwave.
Which of these signs its webhooks?
Paystack signs every webhook with an HMAC SHA512 of the body. Flutterwave v3 sends back a secret hash you choose, in the verif-hash header. MTN MoMo callbacks are not signed. With all three, confirm the payment with the provider's status or verify endpoint before giving value.
Which is cheapest?
None of the three publishes a single price for every market and business, and rates change. Ask each for a quote for your country and volume, and include settlement and withdrawal costs in the comparison.