Skip to content

How a checkout works ​

A checkout is one order. Your server sets the amount. The payment goes to your main pocket. The checkout expires. It can complete once.

A payment link is a standing page. A checkout is not.

Status ​

StatusMeaning
openPayable, or a payment is in flight
completedThe money landed. payment has the receipt
expiredTime passed, and no attempt is in flight

status is computed on each read. The payment attempt says whether money landed. The clock says whether time passed. Your GET and the payer page use the same rule.

There is no failed status and no canceled status. A declined prompt leaves the checkout open. The payer can try again until it expires. payment.status is the last attempt.

Fulfill only when status is completed.

Expiry ​

expires_at is set at create. The default life is 30 minutes. Read expires_at from the response.

An attempt still in flight keeps status at open after expires_at.

On Kenya, an exact M-Pesa Pay Bill payment can set status to completed after expires_at. See below.

payment ​

payment is null until someone tries to pay. After that it is the latest attempt.

FieldMeaning
intent_idAttempt id (pi_…)
statuscreated, reserved, submitted, pending, completed, failed, expired, or reversed
amount_settledMinor units received, as a string. null until it settles
provider_receiptMobile-money receipt. null until it settles
phonePayer phone number

A new attempt replaces the previous payment object.

One attempt at a time ​

While an attempt is in flight, a second prompt is not sent. POST /checkouts/:id/pay returns 200 and the current attempt.

A completed checkout returns 409 and code 2785.

A card payment already in progress returns 409 and code 2786.

The payer page ​

url is the hub page /checkout/<id>.

The page shows your name, the amount, and description. It does not show reference, the receipt, or a previous payer's phone number.

If you send phone at create, the page fills that number. The payer can change it.

The country is in the id: cs_ke_… or cs_ug_…. The page calls that country's host. The payer does not sign in.

M-Pesa menu (Kenya) ​

When Pay Bill is on, the payer page shows two numbers:

Field on the payer checkoutMeaning
paybill.business_numberYour M-Pesa Pay Bill number
paybill.account_numberCS plus 8 digits, for this checkout

The payer can pay the checkout amount from the M-Pesa menu with those numbers.

Pay Bill amountResult
Equal to the checkout amountstatus becomes completed, even after expires_at
Any other amountThe money goes to your main pocket. status stays open or expired

The merchant checkout object does not include paybill. Confirm with GET /checkouts/:id.

After payment ​

When status is completed and you set return_url, the page shows the paid state for 3 seconds. It then opens return_url and sets these query parameters:

ParameterValue
checkoutCheckout id
referenceYour reference, when you set one
statuscompleted

Parameters already on return_url stay. checkout, reference, and status are replaced when those names already exist.

The redirect runs only after completion. Do not fulfill from the query string. Read the checkout.

return_url must use https. Off production, http://localhost and http://127.0.0.1 are allowed.

Fields ​

You sendMOOKHPay sets
amountid, url, currency, status, expires_at
reference, descriptionpayment
return_url, phonePayee: your main pocket

There is no currency field. The host sets it. Kenya is KES. Uganda is UGX.

The amount is checked against the mobile-money rail at create. See Limits.

Staging documentation. Staging charges real money. See Environments.