A non-zero STK result is not one generic failure
When an M-PESA Express request completes, the callback or query result includes a ResultCode and ResultDesc. The business should preserve the raw code, then map it to a business meaning that determines the next action.
The public Daraja pages document the M-PESA Express request/query flow and the use of CheckoutRequestID, but Safaricom's static public pages do not currently expose a complete STK failure-code catalogue. The common codes below are widely observed in Daraja integrations and should still be validated against current sandbox and production responses before hard-coding policy.
Common M-PESA Express outcomes
| ResultCode | Business meaning | Suggested internal state | Typical next action |
|---|---|---|---|
| 0 | Payment succeeded | SUCCEEDED | Close invoice after matching evidence |
| 1032 | Customer cancelled | FAILED / CANCELLED | Let customer retry later; no blind auto-retry |
| 1037 | Prompt timed out / customer unreachable | EXPIRED | Offer a fresh payment action after final result |
| 1 | Insufficient balance | FAILED | Wait/remind later or offer another enabled method |
| 2001 | Wrong PIN commonly reported for STK | FAILED | Return control to customer; never request their PIN |
ResultCode 0: payment succeeded
ResultCode 0 is the clean success path. The callback includes successful payment metadata, including the M-PESA receipt number and amount. The invoice can be closed once the merchant has matched the result to the correct attempt and checked that the amount is acceptable.
ResultCode 1032: customer cancelled
A cancellation is a final unsuccessful outcome for that attempt, but it is not a technical incident. The customer actively dismissed or cancelled the request.
Recommended business action: leave the invoice due, record the attempt as cancelled, do not auto-retry immediately, and let the customer initiate another payment when appropriate.
ResultCode 1037: timeout or unreachable customer
A 1037 outcome is commonly associated with the customer not completing the prompt within the available window or the device being unreachable.
Recommended business action: treat the attempt as expired/failed once the provider has returned the final code. Offer a fresh payment action rather than endlessly retrying in the background.
ResultCode 1: insufficient balance
Insufficient balance means the customer could not complete this attempt because the wallet did not have enough funds.
Recommended business action: keep the invoice due. Do not hammer the wallet with immediate repeated prompts. Let merchant policy decide whether to wait, remind later or offer another enabled method.
ResultCode 2001: wrong PIN
In M-PESA Express integrations, 2001 is commonly reported for an incorrect customer PIN. The payment did not complete.
Recommended business action: record a final unsuccessful attempt and return control to the customer. Do not tell support staff to ask for, collect or troubleshoot the customer's PIN.
Other codes you may see
ResultCode 1001 is commonly reported when another M-PESA transaction is already in progress for the same customer, so the new prompt could not be processed. Treat it as a final failure for that attempt, wait before offering a fresh one, and do not send several prompts to the same phone in quick succession.
ResultCode 1019 is commonly reported when the transaction expired before the customer completed it. Handle it like a timeout: keep the invoice due and offer a fresh payment action.
As with the codes above, confirm these against your own sandbox and production responses before you hard-code policy.
What about a payment with no final result yet?
Do not invent a failure code. If the callback has not arrived, the attempt is unresolved until checked.
For an M-PESA Express request, use M-PESA Express Query with the CheckoutRequestID returned when the prompt was created. Transaction Status is a different API: it checks a recent transaction using an M-PESA receipt number or Originator Conversation ID and is useful when those identifiers exist.
A safer internal state model
- AWAITING_CUSTOMER — prompt created; customer action is still expected.
- PENDING — provider processing is still within the expected window.
- SUCCEEDED — sufficient provider evidence confirms payment.
- FAILED — provider returned a final unsuccessful outcome such as cancellation, insufficient balance or wrong PIN.
- EXPIRED — the request timed out without payment.
- UNKNOWN — no reliable final result is available yet; query before retry.
Do not let result codes become customer copy
Support staff and software should translate provider codes into useful next actions. A customer does not need to see “1037”. They need to know whether the payment completed and what they should do next.
For retry timing by failure reason, use the dedicated retry guide. For developer-level normalization across providers, use the payment-state normalization guide.
Sources
- Safaricom M-PESA Express API: https://developer.safaricom.co.ke/apis/MpesaExpressSimulate
- Safaricom M-PESA Express Query API: https://developer.safaricom.co.ke/apis/MpesaExpressQuery
- Safaricom Transaction Status API: https://developer.safaricom.co.ke/apis/TransactionStatus



