Accept mobile money payments
Mobile money is Ghana's dominant payment method, accounting for over 70% of digital transactions. This guide shows you how to accept payments from MTN, Vodafone, and AirtelTigo wallets through a three-phase flow: create an order, confirm customer intent with an OTP, then wait for payment authorization.
How it works
Mobile money payments follow a three-phase pattern. First, you create an order with execute_payment: true—Commerce sends a 6-digit OTP to the customer's phone. The customer shares this code with you through your app, proving they initiated the transaction. You submit the OTP via /orders/confirm_payment, which transitions the order to authorize_payment status. Commerce then prompts the customer to authorize the charge with their mobile money provider via USSD or SMS. The customer enters their wallet PIN to approve, and the payment completes. The entire flow takes 20-45 seconds. Funds settle to your balance in real-time.
Supported networks
Commerce integrates with all three major networks in Ghana: MTN Mobile Money (24M+ wallets), Vodafone Cash (8M+ wallets), and AirtelTigo Money (6M+ wallets). Commerce automatically routes transactions based on phone number prefix—no network-specific integration needed.
Prerequisites
Get your production API keys from your Commerce dashboard under Settings → API Keys. Test your integration in sandbox mode before going live.
Step 1: Create the order
Creating an order bundles the transaction details—customer information, payment method, and line items—into a single atomic unit. When you set execute_payment: true, Commerce sends a 6-digit OTP to the customer's phone within 2-5 seconds to confirm their intent before initiating the mobile money debit.
Create mobile money order
curl https://api.zebo.dev/orders/new \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"idempotency_key": "order_momo_2025_001",
"execute_payment": true,
"customer_data": {
"name": "Akosua Mensah",
"email_address": "akosua@example.com",
"phone_number": "+233244123456"
},
"payment_method_data": {
"type": "mobile_money",
"mobile_money": {
"issuer": "mtn",
"number": "0244123456"
}
},
"line_items": [
{
"type": "product",
"product": {
"type": "digital",
"name": "Premium Subscription - 1 Month",
"quantity": 1,
"price": { "currency": "ghs", "value": 5000 }
}
}
]
}'
Key parameters
idempotency_key- Unique identifier to prevent duplicate charges. Use order references or UUIDs.execute_payment- Set totrueto initiate payment immediately.payment_method_data.mobile_money.issuer- Network:"mtn","vodafone", or"airteltigo".payment_method_data.mobile_money.number- Wallet phone number (local or international format).- Amounts - In minor units (pesewas). GHS 50.00 =
5000.
The response includes order.id (needed for confirmation), customer.id and payment_method.id (save for future charges), and next_action indicating Commerce sent the OTP. The customer receives the 6-digit code via SMS. The OTP expires in 5 minutes.
Step 2: Confirm customer intent with OTP
The customer receives a 6-digit OTP from Commerce proving they initiated the transaction. Collect this code in your UI and submit it within 5 minutes—after that, the OTP expires. Once you submit the OTP, Commerce transitions the order to authorize_payment status and prompts the customer to approve the charge with their mobile money provider.
Confirm payment
curl https://api.zebo.dev/orders/confirm_payment \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"order_id": "ord_momo_abc123xyz",
"token": "483921"
}'
Commerce validates the OTP and transitions the order to authorize_payment status. The customer then receives a USSD prompt or SMS from their mobile money provider asking them to authorize the charge with their wallet PIN. Once authorized, Commerce debits the wallet and credits your balance in 1-3 seconds. The order status changes to paid.
Common errors:
invalid_otp- Wrong code submitted, customer can retry (3 attempts max)otp_expired- 5-minute window closed, create new orderinsufficient_balance- Customer lacks funds during authorization
Step 3: Verify payment status
After the customer authorizes the payment with their mobile money provider, look up the order to verify completion. This typically happens within 5-15 seconds after authorization:
Check payment status
curl https://api.zebo.dev/orders/lookup \
-H "Authorization: Bearer YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"order_id": "ord_momo_abc123xyz"
}'
Statuses: requires_action (waiting for OTP), paid (success), failed (insufficient balance, cancelled), expired (5-minute window closed).
Important details
Transaction limits: MTN (GHS 5K/transaction, 10K daily), Vodafone (3K/transaction, 5K daily), AirtelTigo (2K/transaction, 3K daily). Exceeding limits returns amount_too_large error.
Settlement: Real-time to your balance, but 7-day aging period before payout eligibility (Bank of Ghana dispute window).
Pricing: 1.5% + GHS 0.50 per transaction, deducted before funds reach your balance.
Network detection: Commerce validates issuer matches phone prefix (MTN: 024/054/055/059, Vodafone: 020/050, AirtelTigo: 027/057/026/056).
Next steps
You're now accepting mobile money payments! Here's what to explore next:
- Charge repeat customers - Save payment methods for one-click checkout
- Handle payment failures - Retry failed transactions gracefully
- Set up payouts - Withdraw funds to your bank account
- Understand payment method settings - Configure verification and confirmation requirements
For detailed parameter documentation, see the Orders API reference.