Main resources
Payments
Create, track and collect Mobile Money payments in USSD mode or via the hosted payment gateway.
The Payment Object
{
"id": "pay_abc123",
"reference": "KPAY-20260514-ABC123",
"providerReference": "f4401bd2-1568-4140-bf2d-eb77d2b2b639",
"status": "COMPLETED",
"amount": 5000,
"netAmount": 4900,
"feeAmount": 100,
"currency": "XAF",
"externalId": "ORDER-12345",
"provider": "MTN_MOMO_CMR",
"country": "CMR",
"phoneNumber": "237653456789",
"isTest": true,
"description": "Paiement commande #12345",
"metadata": { "orderId": "12345" },
"createdAt": "2026-05-14T10:00:00.000Z",
"completedAt": "2026-05-14T10:02:30.000Z",
"failedAt": null,
"failureReason": null
}Properties
idstringUnique payment identifier.
referencestringInternal KPAY reference (source of truth).
providerReferencestring | nullOperation identifier on the operator side (depositId, UUID).
statusstringCurrent payment status.
PENDINGPROCESSINGCOMPLETEDFAILEDCANCELLEDamountnumberGross amount requested, in the operator country currency (XAF, XOF, KES…).
netAmountnumberNet amount credited after commission (payment).
feeAmountnumberCommission charged.
currencystringCurrency derived from the operator country (e.g. XAF, XOF, KES, ZMW).
externalIdstringYour transaction identifier (idempotency).
providerstring | nullProvider (operator) inferred from the number (e.g. MTN_MOMO_CMR, ORANGE_CMR, MTN_MOMO_ZMB).
countrystring | nullOperator country, ISO 3166-1 alpha-3 (e.g. CMR, ZMB, KEN), inferred from the number.
phoneNumberstringPayer's Mobile Money number (normalized format, without + or leading 0).
isTestbooleantrue if the transaction was initiated with a test key (sandbox).
metadataobjectFree data returned as-is.
completedAtstring | nullCompletion timestamp (if COMPLETED).
failureReasonstring | nullFailure reason (if FAILED).
Lifecycle & statuses
PENDING— waiting for validation by the customer.PROCESSING— processing by the provider in progress.COMPLETED— successful (net amount available in the wallet).FAILED— failed (insufficient funds, timeout…).CANCELLED— cancelled by the customer.
USSD Mode — Initiate a payment
POST/api/v1/payments/init
Authentication & environment
kpay_test_xxxxxxxxxxxxxxxx. In sandbox, KPay routes your request to the KPay test environment: use a test number (see the "Test mode" section below). The API URL is the same as in production.Mandatory provider (USSD mode)
Authorization whitelist (Application)
Request body
amountnumberrequisAmount in the provider currency, in whole units unless the provider supports decimals. Minimum 50 XAF in Cameroon zone. A commission is charged.
providerstringrequisMobile Money operator code (e.g. MTN_MOMO_CMR, ORANGE_CMR). Determines the country and currency. See the provider catalogue.
phoneNumberstringrequisMobile Money number in international format (country code + number). In sandbox, a test number; in production, a real number.
externalIdstringrequisYour unique transaction identifier. 409 if already active.
descriptionstringPayment description shown to the customer.
customerNamestringCustomer's full name.
customerEmailstringCustomer's email.
metadataobjectFree JSON metadata, returned in the status.
Request example
const res = await fetch("https://admin.kpay.site/api/v1/payments/init", {
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": 5000,
"provider": "MTN_MOMO_CMR",
"phoneNumber": "237653456789",
"externalId": "ORDER-12345"
}),
});
const data = await res.json();Response (201)
{
"id": "pay_abc123",
"reference": "KPAY-20260514-ABC123",
"providerReference": "f4401bd2-1568-4140-bf2d-eb77d2b2b639",
"status": "PENDING",
"amount": 5000,
"currency": "XAF",
"externalId": "ORDER-12345",
"provider": "MTN_MOMO_CMR",
"country": "CMR",
"phoneNumber": "237653456789",
"isTest": true,
"message": "Paiement initié. Le client doit valider la demande sur son téléphone."
}Hosted gateway mode (GATEWAY)
In GATEWAY mode, KPay hosts the payment page: the customer enters their operator and number themselves. Call /api/v1/payments/init without phoneNumber / paymentMethod / customerName, with returnUrl (required) and cancelUrl (optional).
Request example
const res = await fetch("https://admin.kpay.site/api/v1/payments/init", {
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": 5000,
"externalId": "ORDER-12346",
"returnUrl": "https://monsite.com/return",
"cancelUrl": "https://monsite.com/cancel"
}),
});
const data = await res.json();Response in GATEWAY mode
{
"id": "pay_xyz789",
"reference": "KPAY-20260514-XYZ789",
"externalId": "ORDER-12346",
"status": "PENDING",
"mode": "GATEWAY",
"amount": 5000,
"currency": "XAF",
"gatewayUrl": "https://admin.kpay.site/gateway/gw_8sJ2...",
"expiresAt": "2026-05-16T10:30:00.000Z",
"isTest": true,
"message": "Redirect the customer to gatewayUrl to complete the payment."
}Return redirect (signed query)
{returnUrl}?status=COMPLETED&reference=KPAY-20260514-ABC123&externalId=ORDER-12345&ts=1747245600000&sig=<hmac-sha256-hex>Signature verification (server-side)
Golden rule
const crypto = require("crypto");
function verifyReturn(query, gatewaySecret) {
const { status, reference, externalId = "", ts, sig } = query;
const stringToSign = `${status}|${reference}|${externalId}|${ts}`;
const expected = crypto
.createHmac("sha256", gatewaySecret)
.update(stringToSign)
.digest("hex");
const ok =
sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
return ok && Date.now() - Number(ts) < 10 * 60 * 1000;
}Status tracking (polling)
GET/api/v1/payments/:id
Poll this endpoint to check the current status. Space out calls (e.g. every 3 s) with increasing delay, and stop on a terminal status (COMPLETED, FAILED, CANCELLED). The webhook remains the authority.
const res = await fetch("https://admin.kpay.site/api/v1/payments/pay_abc123", {
method: "GET",
headers: {
"X-API-Key": process.env.KPAY_API_KEY,
"X-Secret-Key": process.env.KPAY_SECRET_KEY,
},
});
const data = await res.json();Test mode (KPay sandbox)
As long as your account is not validated (KYC), you are in sandbox mode and can only generate kpay_test_… keys. KPay then routes your requests to the KPay sandbox (with a test token) — no real money is moved. The flow is identical to production: same URL, same format, same statuses.
The number determines the outcome
Providers : MTN_MOMO_CMR, ORANGE_CMR
Payments (deposits)
| Number (MSISDN) | Result | failureCode |
|---|---|---|
| 237653456019 | FAILED | PAYER_LIMIT_REACHED |
| 237653456029 | FAILED | PAYER_NOT_FOUND |
| 237653456039 | FAILED | PAYMENT_NOT_APPROVED |
| 237653456069 | FAILED | UNSPECIFIED_FAILURE |
| 237653456129 | SUBMITTED | — |
| 237653456789 | COMPLETED | — |
Withdrawals (payouts)
| Number (MSISDN) | Result | failureCode |
|---|---|---|
| 237653456089 | FAILED | RECIPIENT_NOT_FOUND |
| 237653456119 | FAILED | UNSPECIFIED_FAILURE |
| 237653456129 | SUBMITTED | — |
| 237653456789 | COMPLETED | — |