Xtopay
Payment APIs

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 ProviderChannel IdentifierCountry
MTN Ghana Direct Debitmtn-gh-direct-debitGhana
Telecel Ghana Direct Debitvodafone-gh-direct-debitGhana

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 Forbidden error 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.

  1. 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.
  2. Direct Debit Charge (Recurring): Pulls funds directly from the authorized wallet at any time without further customer involvement.
  3. Transaction Status Check: Polls the status of debit requests if a webhook callback is missed.

Service Steps

StepDescription
1Your server initiates a Preapproval Mandate request to POST /v1/mandates.
2The customer authorizes the preapproval request (via USSD PIN confirmation or verifying an OTP code).
3Xtopay fires a webhook notifying your callback URL that the mandate is APPROVED or ACTIVE.
4Whenever a payment is due, your server initiates a Direct Debit Charge request via POST /v1/payments/direct-debit.
5Xtopay processes the charge and dispatches a webhook callback to your server.
6If 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.

DetailDescription
API Endpointhttps://api.xtopay.co/v1/mandates
Request TypePOST
Content Typeapplication/json
AuthenticationBasic Base64(clientId:clientSecret)

Request Parameters

ParameterTypeRequiredDescription
clientReferenceIdStringYesUnique reference generated by the client (max 36 chars, alphanumeric).
customerMsisdnStringYesInternational format number without plus (+) sign (e.g., 233249111411).
channelStringYesChannel identifier: mtn-gh-direct-debit or vodafone-gh-direct-debit.
callbackUrlStringYesURL 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

ParameterTypeDescription
messageStringStatus description descriptive message.
responseCodeStringAPI Response code (e.g. 2000 for accepted preapproval).
dataObjectPayload containing mandate info.
data.xtopayPreApprovalIdStringUnique mandate preapproval reference ID.
data.clientReferenceIdStringClient reference matching the request.
data.verificationTypeStringEither USSD (requires handset PIN prompt approval) or OTP (requires code verification).
data.otpPrefixStringFour-letter prefix of OTP code sent to user (null for USSD).
data.preapprovalStatusStringMandate 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.

DetailDescription
API Endpointhttps://api.xtopay.co/v1/mandates/verifyotp
Request TypePOST
Content Typeapplication/json
AuthenticationBasic Base64(clientId:clientSecret)

Request Parameters

ParameterTypeRequiredDescription
customerMsisdnStringYesInternational format number without plus (+) sign (e.g., 233200000000).
xtopayPreApprovalIdStringYesMandate reference ID received in the initiate response.
clientReferenceIdStringYesUnique reference matching the initiation request.
otpCodeStringYesThe 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/status

Cancel Preapproval

Revoke a customer's authorization. Canceled mandates can no longer be charged.

GET https://api.xtopay.co/v1/mandates/preapproval/:customerMsisdn/cancel

Reactivate Preapproval

Request approval to reactivate a canceled mandate.

POST https://api.xtopay.co/v1/mandates/preapproval/reactivate

Body 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.

DetailDescription
API Endpointhttps://api.xtopay.co/v1/payments/direct-debit
Request TypePOST
Content Typeapplication/json
AuthenticationBasic Base64(clientId:clientSecret)

Request Parameters

ParameterTypeRequiredDescription
CustomerMsisdnStringYesInternational format number without plus (+) sign (e.g., 233200000000).
ChannelStringYesProvider direct debit channel: mtn-gh-direct-debit or vodafone-gh-direct-debit.
AmountFloatYesAmount of money to debit during this transaction (max 2 decimal places, e.g. 0.80 or 15.50).
PrimaryCallbackURLStringYesWebhook URL to receive the asynchronous transaction status update.
DescriptionStringYesTransaction details displayed to the user on billing statements.
ClientReferenceStringYesUnique string reference for order mapping (max 36 characters).
CustomerNameStringNoOptional billing name of customer.
CustomerEmailStringNoOptional 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 CodeDescriptionRequired Action
0000Charge completed successfully.None. Fulfill the purchase or subscription.
0001Request accepted. A callback will be dispatched upon final state.Maintain transaction status as pending. Await webhook.
2000Preapproval actions completed successfully (approved, reactivated, or OTP verified).None. Proceed with mandate tracking.
2001debit failed (insufficient wallet funds, customer wrong PIN, USSD session timeout, transaction invalid).Instruct the customer to top up their wallet or review blockages.
4000Validation errors in the request parameters.Inspect parameter formats (such as E.164 phone formats) and retry.
4070declined by gateway. Gateway fees not configured or charge amount below limits.Verify account thresholds or contact your relationship manager.
4101Permission 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.
4103Permission denied. Account not allowed to transact on this channel.Instruct the customer to contact their carrier.
4204Preapproval request failed.Verify customer details and re-initiate preapproval.

How is this guide?

Edit this page on GitHub
Last updated on June 6, 2026

On this page