Direct Receive Money
Prompt a customer to approve an inbound Mobile Money payment directly via API — no hosted checkout required.
Direct Receive Money
The Direct Receive Money API allows merchants to programmatically initiate a Mobile Money pull (debit prompt) directly on a customer's wallet. The customer receives an on-screen USSD or OTP authorization prompt on their mobile device to approve the charge without being redirected to a checkout webpage.
This API is ideal for in-app checkout flows, programmatic subscription renewals, and API-triggered billing systems.
Security Requirements
To prevent individuals from receiving unsolicited mobile money debit prompts (spam protection), merchants are required to implement at least one of the following security features:
- Locked Fields for Registered Users: Ensure that only verified, registered users in your application can initiate payments. Before payment triggers, they must not be allowed to edit their pre-verified wallet numbers.
- OTP Verification: For guest or unregistered users, send a one-time passcode (OTP) to their mobile number and require verification before triggering the payment request.
Available Channels
Xtopay supports direct mobile money collections across the following major networks:
| Mobile Money Provider | Channel Identifier | Country |
|---|---|---|
| MTN Mobile Money | MTN | Ghana, Uganda, Côte d'Ivoire, Zambia |
| Telecel Cash | TELECEL | Ghana |
| AT Money (AirtelTigo) | AIRTELTIGO | Ghana |
| M-Pesa | MPESA | Kenya, Tanzania |
Business IP Whitelisting
[!WARNING] Whitelisting Required Direct debit API endpoints are live and require strict IP whitelisting. Requests from non-whitelisted IP addresses will return a
403 Forbiddenerror or timeout.Please submit your production server public IP addresses to your Xtopay Retail Systems Engineer for whitelisting. We permit a maximum of four (4) IP addresses per service key.
Understanding the Service Flow
The Direct Receive Money process is asynchronous. Since it requires the mobile subscriber's manual approval (via PIN entry or USSD menus) on their phone, the final transaction status is not immediately available and may take up to 30 seconds.
Service Steps
| Step | Description |
|---|---|
| 1 | Your application server makes a Direct Receive Money request to POST /v1/payments/receive. |
| 2 | Xtopay performs credential authentication and returns a 200 OK or 202 Accepted response with status PROCESSING (ResponseCode 0001). |
| 3 | Xtopay sends a prompt to the customer's device. The customer enters their PIN to authorize the transaction. |
| 4 | Once confirmed, Xtopay dispatches an asynchronous HTTP POST payload to your registered callback URL. |
| 5 | In rare instances where you do not receive the webhook callback within five (5) minutes, you must query the Transaction Status Check API (GET /v1/payments/:reference) to fetch the final state. |
Direct Receive Request Flow
sequenceDiagram
autonumber
actor Customer
participant AppServer as Merchant Server
participant Xtopay as Xtopay API
participant Telco as Telco Gateway
Customer->>AppServer: Initiate payment action
AppServer->>Xtopay: POST /v1/payments/receive (Auth: Bearer secret_key)
Xtopay-->>AppServer: Return processing response (ResponseCode 0001)
Xtopay->>Telco: Send debit instruction
Telco-->>Customer: Trigger USSD PIN prompt / OTP on handset
Customer->>Telco: Enter PIN / approve charge
Telco-->>Xtopay: Authorize & process settlement
Xtopay->>AppServer: Send Webhook Callback (POST payment.succeeded)
Note over AppServer, Xtopay: Fallback: Check status via GET /v1/payments/:reference if callback failsAPI Reference
To initiate a direct mobile money debit request, send an HTTP POST request to the endpoint below.
| Detail | Description |
|---|---|
| API Endpoint | https://api.xtopay.co/v1/payments/receive |
| Request Type | POST |
| Content Type | application/json |
| Authentication | Basic Base64(clientId:clientSecret) |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
amount | Integer | Yes | Specifies the total amount to charge in minor units (e.g. 5000 = GHS 50.00). Decimals are not allowed. |
currency | String | No | Three-letter ISO 4217 currency code (e.g. GHS, KES, USD). Case-insensitive. Defaults to GHS. |
phone | String | Yes | The customer's mobile money wallet number in international format (e.g. +233249111411 or 233249111411). |
provider | String | Yes | The network provider channel code. Available codes: MTN, TELECEL, AIRTELTIGO, MPESA. |
description | String | Yes | Transaction details displayed to the user on their phone. |
customerId | String | No | Xtopay customer ID to link the payment. |
metadata | Object | No | Flat JSON key-value store to attach merchant references (e.g. { "clientReference": "3jL2KlUy3vt21" }). |
Response Parameters
| Parameter | Type | Description |
|---|---|---|
success | Boolean | Indicates if the transaction request was accepted successfully. |
data | Object | Contains details of the initiated payment. |
data.id | String | Unique transaction ID generated by Xtopay. |
data.reference | String | Unique transaction reference string. |
data.status | String | Initial status of the debit (always PROCESSING). |
data.amount | Integer | Amount requested in minor units. |
data.currency | String | Currency code. |
data.phone | String | Ingested wallet number. |
data.provider | String | Ingested provider code. |
data.description | String | Transaction description. |
data.createdAt | String | ISO 8601 timestamp. |
Code Examples
curl https://api.xtopay.co/v1/payments/receive \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"amount": 80,
"currency": "GHS",
"phone": "233200000000",
"provider": "TELECEL",
"description": "Union Dues",
"metadata": {
"clientReference": "3jL2KlUy3vt21"
}
}'Sample Response
200 OK
{
"success": true,
"message": "Transaction pending. Expect callback request for final state",
"responseCode": "0001",
"data": {
"id": "pay_09f84e20a283942e807128e8c21d08d6",
"reference": "3jL2KlUy3vt21",
"status": "PROCESSING",
"amount": 80,
"currency": "GHS",
"phone": "233200000000",
"provider": "TELECEL",
"description": "Union Dues",
"createdAt": "2026-06-06T12:00:00Z"
}
}Webhook Callback
Xtopay dispatches an HTTP POST payload to your registered callback URL once the transaction is finalized by the telco.
Sample Callback (Successful)
{
"ResponseCode": "0000",
"Message": "success",
"Data": {
"Amount": 80,
"Charges": 5,
"AmountAfterCharges": 80,
"Description": "The Telecel Cash payment has been approved and processed successfully",
"ClientReference": "3jL2KlUy3vt21",
"TransactionId": "09f84e20a283942e807128e8c21d08d6",
"ExternalTransactionId": "2116938399",
"AmountCharged": 85,
"OrderId": "09f84e20a283942e807128e8c21d08d6",
"PaymentDate": "2026-06-06T12:00:44Z"
}
}Sample Callback (Failed)
{
"ResponseCode": "2001",
"Message": "failed",
"Data": {
"Amount": 80,
"Charges": 5,
"AmountAfterCharges": 80,
"Description": "FAILED: User entered wrong PIN",
"ClientReference": "3jL2KlUy3vt21",
"TransactionId": "09f84e20a283942e807128e8c21d08d6",
"ExternalTransactionId": "2116938399",
"AmountCharged": 85,
"OrderId": "09f84e20a283942e807128e8c21d08d6",
"PaymentDate": "2026-06-06T12:00:44Z"
}
}Transaction Status Check
Checks the status of a debit transaction. It is mandatory to implement this status check to verify the final transaction state if no webhook callback is received within 5 minutes of initiation.
To poll the status, send an HTTP GET request appending the unique client reference.
| Detail | Description |
|---|---|
| API Endpoint | https://api.xtopay.co/v1/payments/:reference |
| Request Type | GET |
| Content Type | application/json |
| Authentication | Basic Base64(clientId:clientSecret) |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
:reference | String | Yes | The client reference of the transaction (3jL2KlUy3vt21). |
Sample GET Request
curl https://api.xtopay.co/v1/payments/3jL2KlUy3vt21 \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET"Sample Response (Paid)
200 OK
{
"message": "Successful",
"responseCode": "0000",
"data": {
"date": "2026-06-06T12:00:44Z",
"status": "Paid",
"transactionId": "09f84e20a283942e807128e8c21d08d6",
"externalTransactionId": "2116938399",
"paymentMethod": "mobilemoney",
"clientReference": "3jL2KlUy3vt21",
"currencyCode": "GHS",
"amount": 80,
"charges": 5,
"amountAfterCharges": 75,
"isFulfilled": true
}
}Sample Response (Unpaid / Pending)
200 OK
{
"message": "Successful",
"responseCode": "0000",
"data": {
"date": "2026-06-06T12:00:00Z",
"status": "Unpaid",
"transactionId": "09f84e20a283942e807128e8c21d08d6",
"externalTransactionId": null,
"paymentMethod": "mobilemoney",
"clientReference": "3jL2KlUy3vt21",
"currencyCode": "GHS",
"amount": 80,
"charges": 0,
"amountAfterCharges": 80,
"isFulfilled": null
}
}Response Codes & Actions
| Response Code | Description | Required Action |
|---|---|---|
0000 | The transaction has been processed successfully. | None. Fulfill the customer's purchase or services. |
0001 | Request accepted. A webhook callback will be dispatched upon final resolution. | Maintain the transaction status as pending. Await final callback. |
2001 | debit failed (e.g. invalid PIN, user timeout, insufficient wallet funds, reached telco balance limits). | Instruct the user to check wallet balances, resolve blockages, or try another wallet number. |
4000 | Validation errors. Request format is incorrect. | Verify parameter schemas (amounts, international phone prefixes) and retry. |
4070 | declined by gateway. Gateway fees not set or transaction is below minimum thresholds. | Check transaction limits or contact your relationship manager. |
4101 | Permission issue: Public/Private keys mismatch, or direct receive scopes are disabled on this account keys. | Ensure your keys are correctly set. Contact support to confirm that mobilemoney-receive-direct scopes are enabled. |
4103 | Permission denied. Customer account is not allowed to transact on this channel. | Instruct the customer to contact their network provider. |
How is this guide?