Bill payments
Let your customers pay their electricity, water or television bills. The customer pays from their own Mobile Money account; you earn a service commission on every bill settled.
Activation required before your first tests
Who pays what
Three calls, in order
A bill payment always follows this sequence. The first two calls debit no one: they let you show your customer what they owe before committing them.
- List the available services to get the biller's code (ENEO, Camwater…).
- Look up the customer's bill from their account number: amount due, name, due date.
- Show that amount to your customer and let them confirm.
- Trigger the payment. The customer receives a Mobile Money authorisation prompt on their phone.
- The biller confirms, the status becomes COMPLETED and your commission becomes available.
List services
Returns the billers you can pay. Each service's code is the value to pass to the next two calls. Filter by category with the category parameter.
const res = await fetch("https://test.admin.kpay.site/api/v1/bills/services", {
method: "GET",
headers: {
"X-API-Key": process.env.KPAY_API_KEY,
"X-Secret-Key": process.env.KPAY_SECRET_KEY,
},
});
const data = await res.json();categorystringFilter by service category. Available categories are returned in the categories field of this same response.
Response
{
"services": [
{
"code": "eneo_postpaid",
"name": "ENEO Postpaid",
"category": "electricity",
"type": "bill",
"description": "Paiement de facture électricité ENEO postpayée",
"currency": "XAF",
"minAmount": 500,
"maxAmount": 500000
}
],
"categories": ["electricity", "water", "tv"],
"total": 1
}codestringService code, to pass to /lookup and /pay.
namestringBiller label, displayable as-is to your customer.
categorystringService family: electricity, water, tv…
currencystringService currency. XAF for Cameroonian billers.
minAmountnumberMinimum amount this biller accepts. Checked by KPay before any outbound call.
maxAmountnumberMaximum amount this biller accepts. Checked by KPay before any outbound call.
Look up a bill
Queries the biller and returns the amount due along with the customer's name. No transaction is created and no amount is debited: call it to show your customer what they owe before committing them.
const res = await fetch("https://test.admin.kpay.site/api/v1/bills/lookup", {
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({
"serviceCode": "eneo_postpaid",
"accountNumber": "1234567890"
}),
});
const data = await res.json();serviceCodestringrequisService code, obtained from GET /api/v1/bills/services.
accountNumberstringrequisThe customer's account or meter number with the biller.
Response
{
"serviceCode": "eneo_postpaid",
"serviceName": "ENEO Postpaid",
"category": "electricity",
"accountNumber": "******7890",
"customerName": "John Doe",
"amountDue": 15400,
"currency": "XAF",
"dueDate": "2026-06-15",
"billReference": "BILL-2026-0042",
"itemId": "pi-9988"
}customerNamestringCustomer name as registered with the biller. Show it so they can confirm the account is the right one.
amountDuenumberOutstanding amount on this account.
dueDatestringBill due date.
billReferencestringBill reference at the biller.
itemIdstringIdentifier of the specific bill. When returned, pass it through to /pay as-is.
The account number is personal data
Pay a bill
Settles the bill with the biller. The end customer receives a Mobile Money authorisation prompt on their phone and approves the debit. The transaction moves to PROCESSING immediately.
const res = await fetch("https://test.admin.kpay.site/api/v1/bills/pay", {
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({
"serviceCode": "eneo_postpaid",
"accountNumber": "1234567890",
"amount": 15400,
"customerPhone": "237653456789",
"externalId": "BILL-ORDER-12345",
"customerName": "John Doe",
"itemId": "pi-9988"
}),
});
const data = await res.json();serviceCodestringrequisService code, obtained from GET /api/v1/bills/services.
accountNumberstringrequisThe customer's account or meter number with the biller.
amountnumberrequisAmount to settle, in XAF and in major units: 15400 means 15,400 FCFA, no cents. Must respect the service's minAmount and maxAmount bounds.
customerPhonestringrequisThe end customer's Mobile Money number, which will receive the authorisation prompt and be debited.
externalIdstringrequisYour operation identifier. Guarantees idempotency: a second call with the same value returns 409 Conflict.
customerNamestringCustomer name, as shown on the biller's receipt.
customerEmailstringEmail address for the biller to send the receipt to.
itemIdstringBill identifier returned by /lookup. Some billers need it to apply the payment to the right instalment.
metadataobjectYour free-form data, returned as-is in webhooks and status lookups.
Response
{
"id": "a3f1c2d4-5e6f-7890-abcd-ef1234567890",
"reference": "KPAY-BILL-MTN2V8NH-C4EDFDAAE75E",
"providerReference": "BILL_ABC123DEF456",
"externalId": "BILL-ORDER-12345",
"status": "PROCESSING",
"serviceCode": "eneo_postpaid",
"serviceName": "ENEO Postpaid",
"category": "electricity",
"accountNumber": "******7890",
"amount": 15400,
"currency": "XAF",
"commissionAmount": 308,
"isTest": true,
"message": "Paiement de facture initié. Le client va recevoir une demande d'autorisation Mobile Money sur son téléphone."
}idstringKPay identifier of the payment, used to check its status.
referencestringKPay internal reference, source of truth for your reconciliation.
providerReferencestringPayment identifier at the biller.
statusstringCurrent payment status.
amountnumberBill amount settled with the biller.
commissionAmountnumberYour service commission. This is the only amount that touches your wallet.
accountNumberstringAccount number, returned masked.
isTestbooleantrue if the payment was initiated with a test key.
Retry without double payment
Track a payment
Returns the current state of a payment, restricted to your own application's transactions. Prefer webhooks over polling: you are notified as soon as the status changes.
const res = await fetch("https://test.admin.kpay.site/api/v1/bills/a3f1c2d4-5e6f-7890-abcd-ef1234567890", {
method: "GET",
headers: {
"X-API-Key": process.env.KPAY_API_KEY,
"X-Secret-Key": process.env.KPAY_SECRET_KEY,
},
});
const data = await res.json();{
"id": "a3f1c2d4-5e6f-7890-abcd-ef1234567890",
"reference": "KPAY-BILL-MTN2V8NH-C4EDFDAAE75E",
"providerReference": "BILL_ABC123DEF456",
"externalId": "BILL-ORDER-12345",
"status": "COMPLETED",
"serviceCode": "eneo_postpaid",
"serviceName": "ENEO Postpaid",
"accountNumber": "******7890",
"customerName": "John Doe",
"amount": 15400,
"currency": "XAF",
"commissionAmount": 308,
"isTest": true,
"completedAt": "2026-09-04T15:32:11.204Z"
}Possible statuses
| Status | Meaning |
|---|---|
| PENDING | Transaction created, not yet sent to the biller. |
| PROCESSING | Sent. The customer must authorise the debit on their phone. |
| COMPLETED | Bill settled. Your commission is available. |
| FAILED | Failed: customer did not authorise, insufficient funds, or biller refusal. |
| CANCELLED | Cancelled before settlement. |
Bill-specific errors
On top of the codes common to the whole API, these situations are specific to bill payments.
| Code | Cause and fix |
|---|---|
| 400 | Amount outside the service bounds, invalid Mobile Money number, or bill not found at the biller. The message states the exact bound or the reason for refusal. |
| 404 | Unknown service code. Refresh your list via GET /api/v1/bills/services. |
| 409 | A payment with this externalId already exists for your application. |
| 429 | Rate limit exceeded. Bill lookup is capped at 20 requests per minute. |
| 503 | The bill payment service is not yet enabled on your account, or the biller is temporarily unreachable. Contact KPay support. |