// Verify AfriPay webhook signature in Node.js const crypto = require('crypto'); function verifyWebhook(payload, secret, receivedSig, timestamp) { const message = `${timestamp}.${payload}`; const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(message) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(receivedSig), Buffer.from(expected) ); }
AfriPay detects the network from the phone prefix automatically. Pass "network":"auto" (default) and we handle routing.
| Prefix | Country | Network | Currency |
|---|---|---|---|
| +25470/71/72/79 | Kenya | M-Pesa | KES |
| +25473/74 | Kenya | Airtel | KES |
| +25677/78/76 | Uganda | MTN MoMo | UGX |
| +25670/75 | Uganda | Airtel | UGX |
| +23324/25/26 | Ghana | MTN MoMo | GHS |
| +25574/75/76 | Tanzania | M-Pesa (Vodacom) | TZS |
| +23480/81 | Nigeria | Airtel | NGN |
- Sandbox access
- M-Pesa (Kenya)
- Basic documentation
- Community support
- Live payments
- Disbursements
- Webhooks
- Live payments
- M-Pesa + MTN + Airtel
- Webhooks
- Email support
- 3 countries
- Disbursements
- SLA guarantee
- Everything in Starter
- All 4 networks + Snipe
- 7 countries
- Disbursements
- Balance checks
- Priority support
- SLA guarantee
- Everything in Pro
- All 17+ countries
- 99.9% SLA guarantee
- Custom webhooks
- Dedicated Slack support
- Custom rate limits
- Invoice billing
AfriPay API Documentation
AfriPay is a unified mobile money API that lets you accept and send payments across Africa with a single integration. One API key, one request format — M-Pesa, MTN MoMo, Airtel Money, and Snipe all handled for you.
Quick Start
Get your first payment running in under 5 minutes.
Step 1 — Get your API key
Go to the Dashboard, copy your API key, and add it to your environment:
# .env AFRIPAY_KEY=afp_live_your_key_here
Step 2 — Install the SDK (optional)
# Node.js npm install afripay # Python pip install afripay # Or use the REST API directly — no SDK needed
Step 3 — Initiate your first payment
# cURL example — request an M-Pesa STK push curl -X POST http://localhost:3000/v1/pay \ -H "Authorization: Bearer afp_live_your_key" \ -H "Content-Type: application/json" \ -d '{ "phone": "+254712345678", "amount": 500, "currency": "KES", "reference": "ORDER-001", "description": "Payment for Order #001" }'
Step 4 — Handle the webhook callback
AfriPay sends a callback to your callback_url when the payment completes or fails. Register your endpoint and verify the signature:
// Express.js webhook handler app.post('/payments/callback', (req, res) => { const { transaction_id, status, network } = req.body; if (status === 'completed') { // ✓ Fulfil order, send receipt fulfillOrder(transaction_id); } else if (status === 'failed') { // ✕ Notify customer, retry or refund handleFailure(transaction_id); } res.sendStatus(200); // Always ACK });
Authentication
All authenticated endpoints require your API key in the Authorization header as a Bearer token:
Authorization: Bearer afp_live_your_key_here
Key format
AfriPay keys follow this format: afp_{tier}_{32-char-hmac-signed-token}
| Prefix | Tier |
|---|---|
| afp_free_ | Free |
| afp_start_ | Starter |
| afp_pro_ | Pro |
| afp_ultra_ | Ultra |
POST /v1/pay
Initiate a mobile money payment. AfriPay automatically detects the network from the phone prefix and routes to the correct provider.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone | string | required | E.164 format e.g. +254712345678 |
| amount | number | required | Positive number, max 150,000 |
| currency | string | optional | ISO 4217, e.g. KES. Auto-detected from phone if omitted. |
| reference | string | required | Your order/transaction ref, max 20 chars |
| description | string | optional | Shown to customer, max 100 chars |
| callback_url | string | optional | HTTPS URL for payment status callbacks |
| network | string | optional | mpesa · mtn_momo · airtel · snipe · auto (default) |
Response
GET /v1/status/:id
Poll the status of a transaction using its transaction_id returned from /v1/pay or /v1/disburse.
Path Parameters
| Parameter | Type | Description |
|---|---|---|
| id | string | AfriPay transaction ID starting with afp_ |
Status Values
| Status | Meaning |
|---|---|
| pending | Awaiting customer action or provider confirmation |
| completed | Payment successful — safe to fulfil order |
| failed | Payment failed — check failure_reason |
| cancelled | Customer declined or timed out |
POST /v1/disburse
Send money from your business wallet to a mobile money number. Requires Starter plan or above.
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| phone | string | required | Recipient phone in E.164 format |
| amount | number | required | Amount to send, max 500,000 |
| currency | string | optional | Auto-detected if omitted |
| reference | string | required | Your internal reference |
| description | string | optional | Reason for payment |
GET /v1/balance
Returns your current available balance across all configured provider wallets. Requires Pro plan or above.
Webhooks
AfriPay sends a POST request to your callback_url when a transaction status changes. Always respond with HTTP 200 to acknowledge.
Webhook Payload
Networks & Auto-Detection
AfriPay automatically detects the correct payment network from the phone number prefix using Rust-powered pattern matching. You never need to specify the network manually.
See the full prefix table on the Networks page.
Error Codes
| Code | HTTP | Meaning |
|---|---|---|
| VALIDATION_ERROR | 400 | Request body failed validation |
| INVALID_PHONE | 400 | Phone prefix not recognised |
| MISSING_API_KEY | 401 | Authorization header missing |
| INVALID_API_KEY | 401 | Key is malformed or tampered |
| FORBIDDEN | 403 | Transaction belongs to another key |
| TRANSACTION_NOT_FOUND | 404 | No transaction with that ID |
| RATE_LIMIT_EXCEEDED | 429 | Too many requests — check tier limits |
| INSUFFICIENT_FUNDS | 422 | Customer wallet is empty |
| PAYMENT_CANCELLED | 422 | Customer dismissed the prompt |
| PROVIDER_TIMEOUT | 504 | Provider didn't respond in time |
| PROVIDER_ERROR | 502 | Upstream provider returned an error |
| INTERNAL_ERROR | 500 | AfriPay internal error |
Rate Limits
| Plan | Requests / 15 min | Monthly calls |
|---|---|---|
| Free | 100 | 500 |
| Starter | 500 | 5,000 |
| Pro | 2,000 | 50,000 |
| Ultra | 10,000 | Unlimited |
Rate limit headers are included in every response: RateLimit-Remaining, RateLimit-Reset.
Testing
Set MPESA_ENV=sandbox, MTN_MOMO_ENV=sandbox, and AIRTEL_ENV=sandbox in your .env file to use sandbox mode.
Running the test suite
# Install deps and run all tests cd afripay/node-api npm install npm test # With coverage npm run test:coverage
Test phone numbers
Use these numbers in sandbox mode to trigger specific outcomes. Visit the Try It Live page to test interactively.
| Phone | Network | Result |
|---|---|---|
| +254712345678 | M-Pesa KE | Success |
| +256771234567 | MTN Uganda | Success |
| +233241234567 | MTN Ghana | Success |
| +254731234567 | Airtel KE | Insufficient funds |