Direct Debit Money
Pull recurring payments directly from a customer's Mobile Money wallet using preapproved Mandates — no re-approval needed after authorization.
Direct Debit Money
The Direct Debit API allows merchants to collect recurring payments directly from a customer's Mobile Money wallet. The customer authorizes a debit mandate once (via USSD or OTP), and your system can initiate subsequent charges automatically without requiring the customer to enter their PIN or approve individual prompts again.
This is ideal for subscription billing, utility bills, insurance premiums, loan repayments, and recurring charges.
Available Channels
Xtopay supports direct debits from the following Mobile Money networks:
| Mobile Money Provider | Channel Identifier | Country |
|---|---|---|
| MTN Ghana Direct Debit | mtn-gh-direct-debit | Ghana |
| Telecel Ghana Direct Debit | vodafone-gh-direct-debit | Ghana |
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 Debit process consists of two primary asynchronous flows and a fallback status polling API.
- Direct Debit Mandate Preapproval (One-Time): Creates and authorizes a mandate on the customer's wallet. Authorization is completed via mobile USSD prompts or OTP SMS validation.
- Direct Debit Charge (Recurring): Pulls funds directly from the authorized wallet at any time without further customer involvement.
- Transaction Status Check: Polls the status of debit requests if a webhook callback is missed.
Service Steps
| Step | Description |
|---|---|
| 1 | Your server initiates a Preapproval Mandate request to POST /v1/mandates. |
| 2 | The customer authorizes the preapproval request (via USSD PIN confirmation or verifying an OTP code). |
| 3 | Xtopay fires a webhook notifying your callback URL that the mandate is APPROVED or ACTIVE. |
| 4 | Whenever a payment is due, your server initiates a Direct Debit Charge request via POST /v1/payments/direct-debit. |
| 5 | Xtopay processes the charge and dispatches a webhook callback to your server. |
| 6 | If no callback is received within five (5) minutes of charging, query the Transaction Status Check API to verify. |
Direct Debit Request Flow
sequenceDiagram
autonumber
actor Customer
participant AppServer as Merchant Server
participant Xtopay as Xtopay API
participant Telco as Telco Gateway
Note over Customer, Xtopay: Flow 1: Mandate Preapproval (One-Time)
AppServer->>Xtopay: POST /v1/mandates (Initiate Preapproval)
Xtopay-->>AppServer: Return XtopayPreapprovalId & VerificationType (USSD/OTP)
alt VerificationType is USSD
Xtopay->>Telco: Trigger USSD prompt
Telco-->>Customer: PIN Authorization prompt
Customer->>Telco: Confirm PIN
else VerificationType is OTP
Xtopay->>Customer: Send SMS OTP (e.g. prefix RTYE-9231)
Customer->>AppServer: Enter OTP on merchant interface
AppServer->>Xtopay: POST /v1/mandates/verifyotp
Xtopay-->>AppServer: Return OTP Verified
end
Xtopay->>AppServer: Send Preapproval Callback (Preapproval APPROVED)
Note over Customer, Xtopay: Flow 2: Direct Debit Charge (Recurring)
AppServer->>Xtopay: POST /v1/payments/direct-debit (Charge request)
Xtopay-->>AppServer: Return PROCESSING status (ResponseCode 0001)
Xtopay->>Telco: Debit customer wallet directly (No PIN/OTP required)
Telco-->>Xtopay: Debit settlement success
Xtopay->>AppServer: Send Webhook Callback (POST payment.succeeded)Mandate Preapproval APIs
1. Initiate Preapproval
Submit a preapproval request to begin customer wallet authorization.
| Detail | Description |
|---|---|
| API Endpoint | https://api.xtopay.co/v1/mandates |
| Request Type | POST |
| Content Type | application/json |
| Authentication | Basic Base64(clientId:clientSecret) |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
clientReferenceId | String | Yes | Unique reference generated by the client (max 36 chars, alphanumeric). |
customerMsisdn | String | Yes | International format number without plus (+) sign (e.g., 233249111411). |
channel | String | Yes | Channel identifier: mtn-gh-direct-debit or vodafone-gh-direct-debit. |
callbackUrl | String | Yes | URL to receive the asynchronous preapproval status update. |
Code Examples
curl https://api.xtopay.co/v1/mandates \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"clientReferenceId": "3jL2KlUy3vt21debitC",
"customerMsisdn": "233200000000",
"channel": "vodafone-gh-direct-debit",
"callbackUrl": "https://merchant.com/webhooks/mandates"
}'Response Parameters
| Parameter | Type | Description |
|---|---|---|
message | String | Status description descriptive message. |
responseCode | String | API Response code (e.g. 2000 for accepted preapproval). |
data | Object | Payload containing mandate info. |
data.xtopayPreApprovalId | String | Unique mandate preapproval reference ID. |
data.clientReferenceId | String | Client reference matching the request. |
data.verificationType | String | Either USSD (requires handset PIN prompt approval) or OTP (requires code verification). |
data.otpPrefix | String | Four-letter prefix of OTP code sent to user (null for USSD). |
data.preapprovalStatus | String | Mandate state, initially PENDING. |
Sample Response (USSD)
{
"message": "Request received! Pending preapproval",
"responseCode": "2000",
"data": {
"xtopayPreApprovalId": "5f20092321d54eefb974dbfea6de5c34",
"clientReferenceId": "3jL2KlUy3vt21debitC",
"verificationType": "USSD",
"otpPrefix": null,
"preapprovalStatus": "PENDING"
}
}2. Verify OTP Preapproval
If the initiation response returns verificationType: "OTP", you must collect the 4-digit code sent to the customer's phone and submit this verification request.
| Detail | Description |
|---|---|
| API Endpoint | https://api.xtopay.co/v1/mandates/verifyotp |
| Request Type | POST |
| Content Type | application/json |
| Authentication | Basic Base64(clientId:clientSecret) |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
customerMsisdn | String | Yes | International format number without plus (+) sign (e.g., 233200000000). |
xtopayPreApprovalId | String | Yes | Mandate reference ID received in the initiate response. |
clientReferenceId | String | Yes | Unique reference matching the initiation request. |
otpCode | String | Yes | The OTP code composed of the prefix and the sent digits, separated by a hyphen (e.g., HNRM-8852). |
Sample Verify OTP Request
curl https://api.xtopay.co/v1/mandates/verifyotp \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"customerMsisdn": "233200000000",
"xtopayPreApprovalId": "5f20092321d54eefb974dbfea6de5c34",
"clientReferenceId": "3jL2KlUy3vt21debitC",
"otpCode": "HNRM-8852"
}'Sample Response
{
"message": "OTP Verified! Pending preapproval",
"responseCode": "2000",
"data": {
"xtopayPreApprovalId": "5f20092321d54eefb974dbfea6de5c34",
"preapprovalStatus": "PENDING"
}
}3. Preapproval Webhook Callback
Xtopay dispatches a webhook event containing the final mandate status to the callbackUrl registered during initiation.
Sample Callback (Approved)
{
"CustomerMsisdn": "233200000000",
"VerificationType": "USSD",
"PreapprovalStatus": "APPROVED",
"XtopayPreapprovalId": "5f20092321d54eefb974dbfea6de5c34",
"ClientReferenceId": "3jL2KlUy3vt21debitC",
"CreatedAt": "2026-06-06T15:08:16Z"
}4. Mandate Status & Cancellation
Check Mandate Status
Query this fallback endpoint to check preapproval state in case of timeouts.
GET https://api.xtopay.co/v1/mandates/preapproval/:clientReferenceId/statusCancel Preapproval
Revoke a customer's authorization. Canceled mandates can no longer be charged.
GET https://api.xtopay.co/v1/mandates/preapproval/:customerMsisdn/cancelReactivate Preapproval
Request approval to reactivate a canceled mandate.
POST https://api.xtopay.co/v1/mandates/preapproval/reactivateBody parameters:
customerMsisdn(string, required): International wallet number.callbackUrl(string, required): Callback target.
Direct Debit Charge API
Once a mandate is approved (PreapprovalStatus: "APPROVED"), charge the mobile wallet directly without trigger prompts, PINs, or OTP codes.
| Detail | Description |
|---|---|
| API Endpoint | https://api.xtopay.co/v1/payments/direct-debit |
| Request Type | POST |
| Content Type | application/json |
| Authentication | Basic Base64(clientId:clientSecret) |
Request Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
CustomerMsisdn | String | Yes | International format number without plus (+) sign (e.g., 233200000000). |
Channel | String | Yes | Provider direct debit channel: mtn-gh-direct-debit or vodafone-gh-direct-debit. |
Amount | Float | Yes | Amount of money to debit during this transaction (max 2 decimal places, e.g. 0.80 or 15.50). |
PrimaryCallbackURL | String | Yes | Webhook URL to receive the asynchronous transaction status update. |
Description | String | Yes | Transaction details displayed to the user on billing statements. |
ClientReference | String | Yes | Unique string reference for order mapping (max 36 characters). |
CustomerName | String | No | Optional billing name of customer. |
CustomerEmail | String | No | Optional email of customer. |
Sample Charge Request
curl https://api.xtopay.co/v1/payments/direct-debit \
-u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
-H "Content-Type: application/json" \
-d '{
"CustomerName": "Joe Doe",
"CustomerMsisdn": "233200000000",
"Channel": "vodafone-gh-direct-debit",
"Amount": 0.8,
"PrimaryCallbackUrl": "https://merchant.com/webhooks/debits",
"Description": "Union Dues Charge",
"ClientReference": "3jL2KlUy3vt21"
}'Sample Response
200 OK
{
"Message": "Transaction pending. Expect callback request for final state",
"ResponseCode": "0001",
"Data": {
"TransactionId": "09f84e20a283942e807128e8c21d08d6",
"Description": "Union Dues Charge",
"ClientReference": "3jL2KlUy3vt21",
"Amount": 0.8,
"Charges": 0.05,
"AmountAfterCharges": 0.8,
"AmountCharged": 0.85,
"DeliveryFee": 0.0
}
}Direct Debit Charge Webhooks
Xtopay dispatches callback webhook alerts to your registered PrimaryCallbackURL when debits succeed or fail.
Successful Webhook Payload
{
"ResponseCode": "0000",
"Message": "success",
"Data": {
"Amount": 0.8,
"Charges": 0.05,
"AmountAfterCharges": 0.8,
"Description": "The Telecel Cash payment has been approved and processed successfully",
"ClientReference": "3jL2KlUy3vt21",
"TransactionId": "09f84e20a283942e807128e8c21d08d6",
"ExternalTransactionId": "2116938399",
"AmountCharged": 0.85,
"OrderId": "09f84e20a283942e807128e8c21d08d6",
"PaymentDate": "2026-06-06T12:00:44Z"
}
}Response Codes & Actions
| Response Code | Description | Required Action |
|---|---|---|
0000 | Charge completed successfully. | None. Fulfill the purchase or subscription. |
0001 | Request accepted. A callback will be dispatched upon final state. | Maintain transaction status as pending. Await webhook. |
2000 | Preapproval actions completed successfully (approved, reactivated, or OTP verified). | None. Proceed with mandate tracking. |
2001 | debit failed (insufficient wallet funds, customer wrong PIN, USSD session timeout, transaction invalid). | Instruct the customer to top up their wallet or review blockages. |
4000 | Validation errors in the request parameters. | Inspect parameter formats (such as E.164 phone formats) and retry. |
4070 | declined by gateway. Gateway fees not configured or charge amount below limits. | Verify account thresholds or contact your relationship manager. |
4101 | Permission issue: Scopes mobilemoney-receive-direct are disabled on API keys, or keys are incorrect. | Check keys configuration. Request Retail Systems Engineer to enable direct debit scopes. |
4103 | Permission denied. Account not allowed to transact on this channel. | Instruct the customer to contact their carrier. |
4204 | Preapproval request failed. | Verify customer details and re-initiate preapproval. |
How is this guide?