Skip to main content

Collecting Mobile Money Payments

Mobile money is the dominant payment method across much of Africa. This guide covers collecting funds via M-Pesa, MTN MoMo, Airtel Money, and Telebirr, then confirming payment via polling or webhooks.

🚀 Quick Start​

curl -X POST https://api.afriroute.ai/api/v1/payments/mobile-money/collect \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"provider": "mpesa",
"phone": "+254712345678",
"amount": 1500,
"currency": "KES",
"reference": "ORDER-A1029",
"callback_url": "https://example.com/webhooks/payments"
}'

📡 Providers​

ProviderCodeMarketsFlow
M-Pesampesa🇰🇪 🇹🇿STK push prompt
MTN MoMomtn🇬🇭 🇺🇬 🇨🇮 +Approve prompt
Airtel Moneyairtel🇺🇬 🇹🇿 🇿🇲 +USSD/approve
Telebirrtelebirr🇪🇹App/USSD approve

All collections are asynchronous: the customer approves a prompt on their phone, so the result arrives after your API call returns.

🔄 The Collection Flow​

  1. Call collect — triggers a prompt on the customer's phone.
  2. Customer enters their PIN to approve.
  3. You receive the final result via webhook (or polling).
const res = await fetch('https://api.afriroute.ai/api/v1/payments/mobile-money/collect', {
method: 'POST',
headers: { 'Authorization': 'Bearer $AFRIROUTE_API_KEY', 'Content-Type': 'application/json' },
body: JSON.stringify({
provider: 'mtn', phone: '+233244000000', amount: 50, currency: 'GHS',
reference: 'INV-7781', callback_url: 'https://example.com/webhooks/payments'
})
});
const { transaction_id } = await res.json(); // status: pending

🔔 Webhooks (Preferred)​

Let the platform tell you when the customer approves or declines.

app.post('/webhooks/payments', (req, res) => {
const { transaction_id, status, reference } = req.body;
if (status === 'success') fulfillOrder(reference);
if (status === 'failed') notifyCustomer(reference);
res.sendStatus(200);
});

⏳ Status Polling (Fallback)​

If you can't receive webhooks, poll the transaction with backoff until it settles.

import requests, time

def poll(txn_id, timeout=120):
deadline = time.time() + timeout
while time.time() < deadline:
r = requests.get(f'https://api.afriroute.ai/api/v1/payments/{txn_id}',
headers={'Authorization': 'Bearer $AFRIROUTE_API_KEY'})
status = r.json()['status']
if status in ('success', 'failed'):
return status
time.sleep(5)
return 'timeout'

💡 Best Practices​

  • Prefer webhooks; use polling only as a fallback.
  • Treat the API response as pending — never fulfill before confirmation.
  • Use a unique reference per order for reconciliation and idempotency.
  • Set a timeout (~2 min) for unapproved prompts and let the user retry.
  • Validate the phone number's provider matches the chosen provider.
  • Verify webhook signatures before trusting them.

Last Updated: May 2026