Hosted checkout & payment links
The hosted checkout gives your customer a ready-made payment link — you create an invoice, send the customer the returned checkoutUrl, and the page handles M-Pesa STK push, manual M-Pesa, card and bank transfer for you. This is the fastest way to collect a one-off payment without building your own payment UI.
Flow at a glance
1. Create an invoice
POST /v1/checkout/invoices (requires a company Bearer token)
curl -X POST https://sandbox.merchants.taifapay.africa/v1/checkout/invoices \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"amount": 1500,
"currency": "KES",
"accountReference": "INV-1001",
"description": "Order #1001 - 2x widget",
"customerName": "Jane Doe",
"customerEmail": "jane@example.com",
"customerPhone": "254712345678",
"methods": ["mpesa_stk", "card"],
"returnUrl": "https://merchant.example.com/thanks",
"expiresInMinutes": 30
}'
Key fields:
| Field | Required | Notes |
|---|---|---|
amount | yes | What the customer pays — they can never change it. |
accountReference | yes | Your unique reference. Combined with your company's identifier letters it forms the public invoice number (ABC + INV-1001 → ABCINV-1001), so it must be unique within your company. Letters, numbers, ., _, - only. |
externalId | no | Your own transaction id, for reconciliation. |
customerPhone | no | Pre-fills the STK push prompt. Encrypted at rest. |
methods | no | Subset of mpesa_stk, mpesa_manual, card, bank. Omit to offer every method your company has a configured gateway for. |
returnUrl | no | HTTPS URL the customer is sent to after paying. |
expiresInMinutes | no | 1–10080, default 30. |
Response:
{
"message": "Invoice created",
"invoice": {
"transactionId": "a3f1c2d4-5e6b-7a8c-9d0e-1f2a3b4c5d6e",
"checkoutUrl": "https://pay.yourdomain.com/pay/ABCINV-1001",
"invoiceNo": "ABCINV-1001",
"amount": 1500,
"currency": "KES",
"status": "open",
"methods": ["mpesa_stk", "card"],
"expiresAt": "2026-07-17T12:30:00.000Z"
}
}
:::tip The transactionId is stable
The transactionId is created with the invoice and settles whichever method the customer eventually picks. Store it — it is the single id you poll or reconcile against, regardless of payment method.
:::
2. Send the customer to the checkout page
The checkoutUrl (/pay/:invoiceNo on the hosted checkout app) is public and needs no authentication — share it by SMS, email, WhatsApp, or redirect to it from your site. The customer picks a method and completes payment there. The page keeps itself updated (e.g. it shows the STK prompt status and the receipt once paid). See Inside the checkout page for a screen-by-screen tour of what the customer actually sees.
3. Know when it's paid
Two options, use either or both:
- Webhooks (recommended) — you receive a signed event when the transaction completes. See Webhooks.
- Polling —
GET /v1/transactions/{transactionId}untilstatusiscompleteorfailed.
You can also fetch the invoice itself:
curl https://sandbox.merchants.taifapay.africa/v1/checkout/invoices/ABCINV-1001 \
-H "Authorization: Bearer $ACCESS_TOKEN"
which returns the session status (open, processing, complete, failed, expired, or cancelled), the selected method, and timestamps. Invoices are scoped to your company — you can never read another merchant's invoice.
The individual checkout/public/... endpoints (STK, manual M-Pesa, bank, card, wallet) that power the hosted page are called by the checkout page itself — they sit behind CSRF and rate-limit guards tied to a live session and aren't meant for direct server-to-server calls. Integrate via invoice creation + webhook/poll, as shown above.
Expiry
An invoice past its expiresAt can no longer be paid; create a new invoice (with a new accountReference) if the customer still wants to pay.