paylod
Accept a payment

Guides

Accept a payment

.md

Collect money from a customer with an M-Pesa STK Push: what to send, what comes back, and what the customer sees.

Charge a customer. Your server decides the amount, the customer's handset rings, they enter their M-Pesa PIN, and the funds settle to your till.

Send the push

charge.ts
import { Paylod } from "@paylod/node";

const paylod = new Paylod(process.env.PAYLOD_API_KEY!);

const outcome = await paylod.collectAndWait({
  amount: 1500,                     // whole KES, from YOUR order record
  phone: "0712345678",
  accountReference: "INV-2041",     // shown on the prompt and the statement
  description: "Order #2041",
  metadata: { orderId: "2041" },    // opaque, stored with the payment
});

if (outcome.paid) {
  await fulfil("2041", outcome.receipt);
} else {
  await notify(outcome.message);
}

collectAndWait() sends the push, then polls until the payment settles. outcome.paid is the only branch you need: on the other side, outcome.message is a decoded sentence you can show the customer.

Need the paymentId straight away instead of waiting? Use collect(), which returns as soon as the prompt is on the handset. See Handle the result.

What M-Pesa insists on

  • Whole shillings. amount is an integer, 1 to 150,000. Decimals are rejected.
  • A Kenyan number. 0712…, 254712… and +254712… all work; the SDK normalises them.
  • One prompt at a time. A customer already looking at a prompt cannot be sent another.

accountReference is capped at 12 characters, description at 64. Both are validated locally, before the request leaves your process.

Idempotency is automatic

The SDK generates an Idempotency-Key for every call, so a network retry can never double-charge.

To retry the same charge later, persist the key and pass it back:

retry.ts
const ack = await paylod.collect({ amount: 1500, phone: "0712345678" });
await db.orders.update("2041", { idempotencyKey: ack.idempotencyKey });

// Later. Same key, same body: replays the original response, no second prompt.
await paylod.collect({
  amount: 1500,
  phone: "0712345678",
  idempotencyKey: ack.idempotencyKey,
});

Never take the amount from the client

The amount must come from your own order record, on your own server. If a browser or mobile app holds both the API key and the amount, a payer can intercept the request and pay less. That is the one way to be undercut, and it is entirely avoidable. See Secure integration.

Next

Handle the result covers webhooks, and what to check before you fulfil.