Integrating MTN MoMo API Into Your SaaS App: A Developer's Playbook
Mobile money is not a niche feature in Africa — it is the payment rail. Over 50% of sub-Saharan Africa's adults are unbanked, yet hundreds of millions actively use mobile money wallets. If your SaaS product targets Ghana, Uganda, Côte d'Ivoire, or any other MoMo-active market and you are still routing payments exclusively through card processors, you are leaving real revenue on the table.
This playbook walks you through a production-grade MTN MoMo integration — sandbox to live — covering the parts that trip up even experienced developers.
Understanding the MoMo API Product Suite
MTN's MoMo Developer API is organised into distinct products. Before writing a single line of code, know which one you need:
- Collections — charge a customer's MoMo wallet (the most common use case for SaaS subscriptions and one-time purchases)
- Disbursements — pay out to a MoMo wallet (payroll, marketplace seller payouts, refunds)
- Remittances — cross-border transfers, typically for licensed entities
Most SaaS integrations start with Collections. Each product has its own base URL, subscription key, and API user credentials — they are not interchangeable.
Sandbox Setup: The Right Way
The sandbox is where most developers waste hours. The documentation is sparse, so follow this sequence exactly.
Step 1 — Create a developer account
Register at momodeveloper.mtn.com. Subscribe to the Collections product (or whichever product you need). You will receive a Subscription Key (Ocp-Apim-Subscription-Key). Guard this — it identifies your app across all API calls.
Step 2 — Create an API User
The sandbox does not auto-provision a user. You must call the provisioning endpoint yourself:
# Replace {subscription-key} with your actual key
# X-Reference-Id must be a UUID v4 you generate
curl -X POST https://sandbox.momodeveloper.mtn.com/v1_0/apiuser \
-H "X-Reference-Id: <your-uuid-v4>" \
-H "Ocp-Apim-Subscription-Key: <your-subscription-key>" \
-H "Content-Type: application/json" \
-d '{"providerCallbackHost": "https://your-domain.com"}'
Save that UUID — it becomes your API User ID. Next, generate the API Key for that user:
POST /v1_0/apiuser/{X-Reference-Id}/apikey
You now have three credentials: Subscription Key, API User ID, and API Key. Store them in environment variables, never in source code.
Step 3 — Get a Bearer Token
Combine API User ID:API Key, Base64-encode the pair, and POST to /collection/token/. Tokens expire after one hour. Build a lightweight token cache in your backend — do not fetch a new token on every transaction request.
Making a Collections Request (Request to Pay)
The core operation is POST /collection/v1_0/requesttopay. Two headers here are critical:
X-Reference-Id— a UUID v4 you generate per transaction. This is your idempotency key.X-Target-Environment—sandboxorproduction
{
"amount": "50",
"currency": "GHS",
"externalId": "order-8821",
"payer": {
"partyIdType": "MSISDN",
"partyId": "233XXXXXXXXX"
},
"payerMessage": "Payment for Pro Plan",
"payeeNote": "SaaS subscription - May 2025"
}
A 202 Accepted response does not mean the payment succeeded. It means the request was received. The actual outcome arrives asynchronously — which brings us to the most misunderstood part of this integration.
Webhook Verification and Async Handling
MTN pushes transaction results to your providerCallbackHost. However, in production, callbacks can arrive out of order, be delayed by minutes, or occasionally not arrive at all. Your integration must handle all three scenarios.
Webhook endpoint checklist:
- Respond with
200 OKimmediately — do not perform heavy processing synchronously inside the webhook handler - Push the raw payload to a queue (Redis, BullMQ, SQS — your choice) and process asynchronously
- Validate the
X-Reference-Idin the callback against your database before updating order status - Implement a polling fallback: if a callback has not arrived within five minutes, call
GET /collection/v1_0/requesttopay/{referenceId}to fetch the current status
Never rely on callbacks alone in a financial system. Polling as a safety net is not optional.
Idempotency: The Silent Bug Killer
Mobile money networks are inherently retryable — network drops are common. If your backend retries a requesttopay call with a new X-Reference-Id, you risk double-charging the customer.
The correct pattern:
- Generate the
X-Reference-Idbefore the API call - Persist it to your database alongside the order, before making the API call
- On any retry, reuse the same
X-Reference-Id - Check your DB first — if a record with that reference already exists in a terminal state (
SUCCESSFULorFAILED), do not call the API again
This single discipline eliminates the most common class of billing bugs in MoMo integrations.
Production Gotchas the Documentation Won't Mention
After shipping MoMo integrations, a few hard-learned lessons stand out:
Phone number formatting is strict. The partyId must be in international format without the + sign — 233244XXXXXX for Ghana, not 0244XXXXXX or +233244XXXXXX. Normalise all inputs before they hit the API.
Currency codes are market-specific. Ghana uses GHS, Uganda uses UGX. Sending the wrong currency code returns a vague error. Build a country-to-currency map and validate early.
Production credentials are not instant. After submitting your go-live application, approval can take days to weeks depending on your market. Do not plan a hard launch without buffer time.
Rate limits exist and are underdocumented. Implement exponential backoff on 429 and 503 responses. In high-volume scenarios (bulk disbursements, end-of-month payroll), throttle your outbound request rate proactively.
Sandbox phone numbers for testing are fixed. MTN provides specific MSISDN values that simulate successful and failed transactions in the sandbox. Using random numbers returns inconsistent results — check the developer portal for the current test number list.
Structuring Your Integration Layer
Avoid coupling MoMo API calls directly to your business logic. A clean abstraction looks like:
- PaymentGateway interface — defines
initiateCollection,checkStatus,initiateDisbursement - MoMoAdapter — implements the interface, handles token caching, request signing, and error normalisation
- WebhookProcessor — stateless handler that validates, enqueues, and acknowledges
- ReconciliationJob — scheduled job that polls for unresolved transactions older than a configurable threshold
This structure lets you swap in Orange Money, Vodafone Cash, or a card processor alongside MoMo without rewriting business logic.
Why This Matters for Your Project
If you are building or scaling a SaaS product for African markets, payment infrastructure is not a backend detail — it is a growth lever. A clean, resilient MoMo integration directly reduces checkout drop-off, unlocks customers who have never owned a bank account, and positions your product as genuinely built for the market rather than ported into it. Getting the fundamentals right — idempotency, async handling, reconciliation — from day one means you will not be firefighting billing incidents when you scale.




