Main resources

Cross-border

Transfer funds between wallets in different countries and send cross-border payouts from any wallet.

Sandbox mode. Use your kpay_test_... keys and the test numbers below. KPay routes your requests to its test environment — no real money is moved.

Overview

KPay isolates funds by country: payments received in Cameroon feed the CMR wallet, those received in Senegal the SEN wallet, etc. — even if the currency is the same (e.g. XAF).

Cross-border operations let you:

  • Transfer funds between wallets — move your balance from one country to another (e.g. CMR → GAB), with automatic conversion if currencies differ.
  • Cross-country payout — send a Mobile Money withdrawal to country B by debiting the country A wallet, with automatic currency conversion if needed.

Sandbox & Production

Both operations work in sandbox (key kpay_test_…) and in production (key kpay_live_…). In sandbox, use test numbers for payouts and funds are virtual.

Inter-wallet transfer

Moves funds from one country's wallet to another country's wallet, within the same application. If currencies differ (e.g. XAF → XOF), KPay applies the real-time exchange rate.

Endpoint

POST/api/wallets/transfer

JWT Authentication

This endpoint uses JWT authentication (Bearer token), not API keys directly. Obtain a token via POST /api/v1/payments/token by exchanging your API keys, then pass it in the header. Authorization: Bearer <token>.

The environment is automatic: a token generated with a kpay_test_... key will operate on sandboxwallets, a token generated with a kpay_live_... key will operate on production. wallets. You don't need to specify anything.

Where to find the applicationId?

Two methods:
  • Via the API — call GET /api/v1/payments/me with your API keys. The response contains application.id.
  • From the dashboard — on the My Applicationspage, each card has a button to copy the ID.

Parameters

Request body

applicationIdstringrequis

Application ID (UUID). Retrieve it via GET /api/v1/payments/me or from the dashboard.

sourceCountrystringrequis

Source wallet country to debit (ISO 3166-1 alpha-3, e.g. CMR, GAB, SEN). Currency is deduced automatically.

destinationCountrystringrequis

Destination wallet country to credit (ISO 3166-1 alpha-3). Currency is deduced automatically.

amountnumberrequis

Amount to transfer in the SOURCE country's currency (e.g. 10000 XAF). Min 100, max 10,000,000. If currencies differ, the credited amount is converted at the exchange rate.

descriptionstring

Description / reason for the transfer.

externalIdstring

Unique identifier on your system's side. A second call with the same externalId returns 409 Conflict (idempotency).

Example — Same currency (XAF → XAF)

Transfer 50,000 XAF from the Cameroon wallet to the Gabon wallet (both in XAF — no conversion).

POST /api/wallets/transfer
{
  "applicationId": "e7c3b4f5-8d9e-4a1b-9c2d-3e4f5a6b7c8d",
  "sourceCountry": "CMR",
  "destinationCountry": "GAB",
  "amount": 50000,
  "description": "Fund transfer CMR to GAB",
  "externalId": "TRF-2026-001"
}
Response (201 Created)
{
  "id": "a1b2c3d4-...",
  "sourceTransaction": {
    "id": "a1b2c3d4-...",
    "reference": "TRF-OUT-ABC123",
    "type": "WALLET_TRANSFER_OUT",
    "amount": 50000,
    "currency": "XAF",
    "country": "CMR"
  },
  "destinationTransaction": {
    "id": "e5f6a7b8-...",
    "reference": "TRF-IN-ABC123",
    "type": "WALLET_TRANSFER_IN",
    "amount": 50000,
    "currency": "XAF",
    "country": "GAB"
  },
  "exchangeRate": 1,
  "description": "Fund transfer CMR to GAB",
  "externalId": "TRF-2026-001",
  "createdAt": "2026-06-23T10:00:00.000Z"
}

Example — Cross-currency (XAF → XOF)

Transfer 100,000 XAF from the Cameroon wallet to the Senegal wallet (XOF). The credited amount is converted at the real-time exchange rate.

POST /api/wallets/transfer
{
  "applicationId": "e7c3b4f5-8d9e-4a1b-9c2d-3e4f5a6b7c8d",
  "sourceCountry": "CMR",
  "destinationCountry": "SEN",
  "amount": 100000,
  "description": "Supply Senegal wallet",
  "externalId": "TRF-2026-002"
}
Response (201 Created)
{
  "id": "c3d4e5f6-...",
  "sourceTransaction": {
    "id": "c3d4e5f6-...",
    "reference": "TRF-OUT-DEF456",
    "type": "WALLET_TRANSFER_OUT",
    "amount": 100000,
    "currency": "XAF",
    "country": "CMR"
  },
  "destinationTransaction": {
    "id": "g7h8i9j0-...",
    "reference": "TRF-IN-DEF456",
    "type": "WALLET_TRANSFER_IN",
    "amount": 101530,
    "currency": "XOF",
    "country": "SEN"
  },
  "exchangeRate": 1.0153,
  "description": "Supply Senegal wallet",
  "externalId": "TRF-2026-002",
  "createdAt": "2026-06-23T10:05:00.000Z"
}

Exchange rate

The rate is fetched in real time and cached for 1 hour. The exchangeRate field in the response lets you verify the applied rate. For same-currency transfers (e.g. XAF → XAF between CMR and GAB), the rate is always 1.

Error codes

Possible errors

400Bad Request

Source country = destination, unsupported country, insufficient balance, or invalid data.

403Forbidden

The application does not belong to the connected user.

404Not Found

No wallet found for the source country (you have never received a payment in this country).

409Conflict

A transfer with this externalId already exists (idempotency).

Cross-country payout

The cross-country payout allows you to send money (Mobile Money) to a beneficiary in country B, while debiting the wallet of country A. This is done via the sourceCountry parameter of the existing withdrawal endpoint.

Endpoint

POST/api/v1/payments/withdraw

Authentication

Authenticate with your kpay_test_xxxxxxxxxxxxxxxxkey. In sandbox, use a test number for the beneficiary.

sourceCountry parameter

The sourceCountry parameter is the only addition compared to a standard withdrawal. It indicates which wallet to debit, regardless of the beneficiary's country.

Additional parameter

sourceCountrystring

ISO 3166-1 alpha-3 country code of the wallet to debit (e.g. CMR). By default, the system debits the wallet of the provider's country. Use this field to debit a wallet from another country.

Automatic currency conversion

If the source wallet currency differs from the destination provider (e.g. wallet CMR (XAF) to provider MTN_MOMO_CIV (XOF)), the conversion is automatic at the real-time exchange rate. The response includes the fields payoutCurrency, payoutAmount and exchangeRate.

Example — Cross-country payout

Send 10,000 XAF to an Orange number in Gabon, debiting the Cameroon wallet (same currency XAF).

Node.js
const res = await fetch("https://admin.kpay.site/api/v1/payments/withdraw", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.KPAY_API_KEY,
    "X-Secret-Key": process.env.KPAY_SECRET_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "amount": 10000,
  "provider": "ORANGE_GAB",
  "phoneNumber": "237653456789",
  "sourceCountry": "CMR",
  "externalId": "PAYOUT-CROSS-001",
  "description": "Supplier payment Gabon"
}),
});
const data = await res.json();
Response — same currency (201 Created)
{
  "id": "wdr_abc789",
  "reference": "KPAY-WD-20260623-ABC789",
  "status": "PENDING",
  "amount": 10000,
  "netAmount": 9500,
  "feeAmount": 500,
  "currency": "XAF",
  "isTest": true,
  "message": "Withdrawal request received. Processing via Mobile Money."
}

Cross-currency payout (automatic conversion)

Debit a CMR (XAF) wallet to pay a beneficiary in Côte d'Ivoire (XOF). The conversion is automatic.

Node.js
const res = await fetch("https://admin.kpay.site/api/v1/payments/withdraw", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.KPAY_API_KEY,
    "X-Secret-Key": process.env.KPAY_SECRET_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
  "amount": 50000,
  "provider": "MTN_MOMO_CIV",
  "phoneNumber": "237653456789",
  "sourceCountry": "CMR",
  "externalId": "PAYOUT-CROSS-002",
  "description": "Supplier payment Côte d'Ivoire"
}),
});
const data = await res.json();
Cross-currency response (201 Created)
{
  "id": "wdr_cross123",
  "reference": "KPAY-WD-20260626-CROSS123",
  "status": "PENDING",
  "amount": 50000,
  "netAmount": 47500,
  "feeAmount": 2500,
  "currency": "XAF",
  "payoutCurrency": "XOF",
  "payoutAmount": 48211.75,
  "exchangeRate": 1.0150,
  "isTest": true,
  "message": "Withdrawal request received. Processing via Mobile Money."
}

Cross-currency fields

The fields payoutCurrency, payoutAmount and exchangeRate only appear when the source wallet currency differs from the destination provider. For same-currency payouts, the response remains identical to a standard payout.

Default behavior (without sourceCountry)

Without sourceCountry, the system debits the wallet of the provider's country. A withdrawal to MTN_MOMO_CMR debits the CMR wallet, a withdrawal to ORANGE_GAB debits the GAB wallet. This is the standard behavior.

Usage scenarios

Scenario 1 — Direct payout (same currency)

Your customers pay in Cameroon (XAF). You want to pay a supplier in Gabon (XAF). Same currency → direct payout with sourceCountry.

Flow
Wallet CMR (XAF: 100 000)
    └──► POST /api/v1/payments/withdraw
         { provider: "ORANGE_GAB", sourceCountry: "CMR", amount: 10000 }
              └──► Beneficiary receives 9 500 XAF on Orange Gabon

Cross-currency payout (single call)

Debit an XAF wallet to directly pay a beneficiary in a country with a different currency. The conversion is automatic.

Flow
Wallet CMR (XAF: 100 000)
    └──► POST /api/v1/payments/withdraw
         { provider: "MTN_MOMO_CIV", sourceCountry: "CMR", amount: 50000 }
              ├── Fees: 2 500 XAF (5%)
              ├── Net: 47 500 XAF
              ├── Rate XAF → XOF: 1.0150
              └──► Beneficiary receives ≈ 48 212 XOF on MTN Côte d'Ivoire

Scenario 3 — Consolidate funds

You receive payments in multiple countries and want to centralize all your funds on a single wallet (e.g. CMR) to simplify management and withdrawals.

Flow
POST /api/wallets/transfer { sourceCountry: "GAB", destinationCountry: "CMR", amount: 30000 }
POST /api/wallets/transfer { sourceCountry: "COG", destinationCountry: "CMR", amount: 15000 }

  Wallet CMR ← consolidation of all funds XAF

Supported countries and currencies

Each country has a primary currency. Transfers between countries with the same currency are instant (1:1 rate). Cross-currency transfers use the real-time exchange rate.

ZoneCountryCodeCurrency
CEMAC (XAF)CameroonCMRXAF
GabonGABXAF
Congo-BrazzavilleCOGXAF
Intra-CEMAC transfers: 1:1 rate
UEMOA (XOF)SenegalSENXOF
Côte d'IvoireCIVXOF
BeninBENXOF
Intra-UEMOA transfers: 1:1 rate
OthersKenyaKENKES
DR CongoCODCDF
UgandaUGAUGX
RwandaRWARWF

Monetary zones

Transfers within the same monetary zone (CEMAC or UEMOA) use a fixed rate of 1:1. Cross-zone transfers (e.g. XAF → XOF, XAF → GHS) are converted at the real-time exchange rate, cached for 1 hour.

Wave operators temporarily suspended

The Wave operators (Côte d'Ivoire and Senegal) are temporarily suspended while their new authentication protocol integration is being finalized. Other operators in these countries remain available.

Best practices

  • Idempotency: always use an externalId for your cross-country transfers and payouts. This prevents duplicates in case of network retry.
  • Check the balance: before a transfer, check the source wallet balance via GET /api/wallets/overview (dashboard) or GET /api/v1/payments/balance (API).
  • Prefer the direct payout with sourceCountry when the currency is the same. The inter-wallet transfer is only necessary for cross-currency operations.
  • Exchange rate: the rate is fixed at the time of transfer and returned in the response. Keep it for your accounting.

Related resources

Was this page helpful?