API reference
QR codes, C2B registration and M-Pesa queries
Generate an M-Pesa QR code, register offline C2B URLs, and query transaction status or account balance.
These endpoints cover the rest of the Daraja surface. They use the same hosted model. They use the same base URL and the same bearer token. No request carries a shortcode.
[!note] No paylod SDK wraps these four endpoints yet. The SDKs cover collect and status only. Call these endpoints with the HTTP client of your language, as below.
POST /qr-generate
Generate a dynamic Lipa na M-Pesa QR code for a given amount. This endpoint is synchronous. It returns a base64-encoded PNG. Show that PNG to the customer to scan. This endpoint is stateless and creates no ledger row. When the customer pays, the payment arrives later on your C2B confirmation URL with source c2b. A signed webhook also fires, like any other offline payment.
POST https://paylod.dev/functions/v1/qr-generateBody
| Field | Type | Required | Description |
|---|---|---|---|
amount | number | Required | Amount to encode in the QR. Must be greater than 0. |
refNo | string | Optional | Reference shown to the customer. Up to 64 chars. |
merchantName | string | Optional | Merchant / display name. Up to 64 chars. |
trxCode | string | Optional | BG Buy Goods (till), PB Pay Bill, WA Withdraw at Agent, SB Send to Business, SM Send Money. Defaults to BG. |
cpi | string | Optional | Credit Party Identifier (till / paybill). Defaults to the tenant shortcode. |
size | string | Optional | Requested QR image size in pixels. |
const res = await fetch("https://paylod.dev/functions/v1/qr-generate", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAYLOD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
amount: 500,
refNo: "INV-2041",
merchantName: "Acme Ltd",
trxCode: "BG"
}),
});
const body = await res.json();{
"qrBase64": "iVBORw0KGgoAAAANSUhEUgAAAP…"
}POST /c2b-register
Register the offline C2B confirmation URL and validation URL for your shortcode. C2B covers Lipa na M-Pesa paybill and till payments. This endpoint is idempotent, so you can run it again safely. It needs no body. paylod derives the shortcode and the callback URL from your API key on the server.
After you register, M-Pesa delivers offline customer payments to paylod as C2B confirmations. paylod records each one as a payment with source c2b. A signed webhook then fires. That webhook uses the same envelope as STK payments.
POST https://paylod.dev/functions/v1/c2b-registerconst res = await fetch("https://paylod.dev/functions/v1/c2b-register", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAYLOD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
const body = await res.json();{
"registered": true,
"shortcode": "600123",
"confirmationUrl": "https://paylod.dev/functions/v1/c2b-hook/8f3c…",
"validationUrl": "https://paylod.dev/functions/v1/c2b-hook/8f3c…",
"responseCode": "0",
"responseDescription": "Success"
}Both URLs point at the same endpoint, /c2b-hook/<token>. The path token is unguessable. Safaricom POSTs both validation and confirmation to that endpoint. responseCode and responseDescription echo the Daraja acknowledgement. Both fields can be null if the shortcode already had a registration.
POST /transaction-status
Read the status of any past M-Pesa transaction by its receipt. This endpoint is asynchronous. The HTTP call acknowledges with 202. The answer arrives on a signed webhook of type admin.transaction_status. You must set the application's Initiator Name and Initiator Password.
POST https://paylod.dev/functions/v1/transaction-statusBody
| Field | Type | Required | Description |
|---|---|---|---|
transactionId | string | Required | The M-Pesa receipt or TransactionID to read. |
identifierType | string | Optional | 1 MSISDN, 2 till, 4 shortcode. Defaults to 4. |
remarks | string | Optional | Free-text remarks for the query. |
occasion | string | Optional | Occasion label. |
const res = await fetch("https://paylod.dev/functions/v1/transaction-status", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAYLOD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
transactionId: "UG1F3A1U7J",
identifierType: "4"
}),
});
const body = await res.json();{
"queryId": "b1e2c3d4-…",
"conversationId": "AG_20260707_00001a2b3c4d"
}Correlate the webhook with queryId or conversationId.
POST /account-balance
Read your shortcode's M-Pesa float. This endpoint is asynchronous. The HTTP call acknowledges with 202. The answer arrives on a signed webhook of type admin.account_balance. You must set the application's Initiator Name and Initiator Password.
POST https://paylod.dev/functions/v1/account-balanceBody
| Field | Type | Required | Description |
|---|---|---|---|
identifierType | string | Optional | 1 MSISDN, 2 till, 4 shortcode. Defaults to 4. |
remarks | string | Optional | Free-text remarks for the query. |
const res = await fetch("https://paylod.dev/functions/v1/account-balance", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.PAYLOD_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
const body = await res.json();{
"queryId": "9a8b7c6d-…",
"conversationId": "AG_20260707_00005e6f7a8b"
}