Skip to main content

Mobile Money Integration

Accept mobile money payments across Africa — Telebirr, M-Pesa, MTN MoMo, and Airtel Money — through a single API with real-time webhooks.

Overview​

Mobile money is the dominant payment rail in most African markets. AfriRoute abstracts each provider behind one consistent flow:

  1. Initiate a payment for a customer's phone number
  2. The customer approves via USSD push or their wallet app
  3. AfriRoute sends a webhook and you fulfill the order

Base URL: https://api.afriroute.ai — Auth: Authorization: Bearer $AFRIROUTE_API_KEY

Supported Providers​

ProviderCountriesCurrency
TelebirrEthiopiaETB
M-PesaKenya, TanzaniaKES, TZS
MTN MoMoGhana, Uganda, Rwanda, ZambiaGHS, UGX, RWF, ZMW
Airtel MoneyKenya, Tanzania, Uganda, ZambiaKES, TZS, UGX, ZMW

Endpoints​

Initiate Payment​

POST /v1/payments/mobile/initiate
FieldTypeRequiredDescription
amountnumberYesAmount in major units
currencystringYesISO 4217, e.g. ETB, KES
phone_numberstringYesCustomer phone (E.164)
providerstringYestelebirr, mpesa, mtn_momo, airtel_money
descriptionstringNoShown on the customer prompt
callback_urlstringNoWebhook for status updates
metadataobjectNoYour reference data
curl -X POST https://api.afriroute.ai/api/v1/payments/mobile/initiate \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 500,
"currency": "ETB",
"phone_number": "+251911234567",
"provider": "telebirr",
"description": "Order #12345",
"callback_url": "https://yourapp.com/payment/callback",
"metadata": { "order_id": "12345" }
}'
const res = await fetch('https://api.afriroute.ai/api/v1/payments/mobile/initiate', {
method: 'POST',
headers: { 'Authorization': 'Bearer $AFRIROUTE_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({
amount: 1000,
currency: 'KES',
phone_number: '+254712345678',
provider: 'mpesa',
description: 'Subscription renewal'
})
});
const payment = await res.json();
console.log(payment.payment_id, payment.status); // pay_abc123 pending
import requests

r = requests.post(
'https://api.afriroute.ai/api/v1/payments/mobile/initiate',
headers={'Authorization': 'Bearer $AFRIROUTE_API_KEY'},
json={
'amount': 50,
'currency': 'GHS',
'phone_number': '+233241234567',
'provider': 'mtn_momo'
}
)
print(r.json())
{
"payment_id": "pay_abc123xyz",
"status": "pending",
"amount": 500,
"currency": "ETB",
"provider": "telebirr",
"created_at": "2026-05-28T09:30:00Z",
"expires_at": "2026-05-28T09:35:00Z"
}

Check Status​

GET /v1/payments/mobile/:payment_id
curl https://api.afriroute.ai/api/v1/payments/mobile/pay_abc123xyz \
-H "Authorization: Bearer $AFRIROUTE_API_KEY"
{
"payment_id": "pay_abc123xyz",
"status": "completed",
"amount": 500,
"currency": "ETB",
"provider_reference": "TXN123456789",
"completed_at": "2026-05-28T09:31:23Z"
}

Statuses: pending, processing, completed, failed, cancelled, expired.

Webhook Handler​

const crypto = require('crypto');

app.post('/payment/callback', (req, res) => {
const signature = req.headers['x-afriroute-signature'];
const hash = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(JSON.stringify(req.body)).digest('hex');
if (hash !== signature) return res.status(401).send('Invalid signature');

const { event, data } = req.body;
if (event === 'payment.completed') fulfillOrder(data.metadata.order_id);
res.sendStatus(200);
});

Best Practices​

  • Always verify the X-AfriRoute-Signature HMAC before trusting a webhook.
  • Rely on webhooks for final status; poll only as a fallback after expires_at.
  • Show the customer the exact amount and provider before initiating to reduce abandonment.
  • Respect provider transaction limits (e.g. M-Pesa 250,000 KES per transaction).

Error Handling​

CodeDescriptionAction
INSUFFICIENT_FUNDSCustomer wallet lowAsk them to top up
INVALID_PHONEWrong number formatValidate to E.164
PROVIDER_UNAVAILABLEProvider outageOffer another method
AMOUNT_TOO_LOWBelow provider minimumShow minimum amount

Last Updated: May 2026