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

POST
/orders/new
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 to true to 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

POST
/orders/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 order
  • insufficient_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

POST
/orders/lookup
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:

For detailed parameter documentation, see the Orders API reference.

Was this page helpful?