M-Pesa Daraja (Safaricom) · Error reference
M-Pesa Daraja Error Codes: 1032, 1037, 2001, 404.001.03 Explained
Safaricom's Daraja API uses dotted error codes like 404.001.03 for rejected requests, and numeric ResultCodes like 1032 in the STK push callback.
Have a response you don't understand? Paste it into the Payment Error Code Lookup and it finds every known code inside.
How M-Pesa reports errors
- API errors come back immediately as JSON with "errorCode" and "errorMessage", for example 404.001.03 "Invalid Access Token".
- STK push outcomes arrive later in the callback as Body.stkCallback.ResultCode and ResultDesc. 0 means paid. Anything else means not paid, or not yet known.
- ResultCode can be a number in one response and a string in another (the callback vs the STK query). Compare as strings.
How sure is this?
Each code below is labelled by how well it is documented. Of the 18 codes on this page: 9 are confirmed by several independent sources, and 9 are reported by only one source. Treat codes marked "Reported by one source" as a strong hint, and log the full response so you can check.
Payment outcomes
The status values that tell you whether the customer paid.
0 Success
PaidSeen in: CallbackRetry: not neededConfirmed by several sources
The customer entered their PIN and the payment went through.
What to do
- Record MpesaReceiptNumber from CallbackMetadata, check the Amount matches the order, then fulfil it once.
- Callbacks can arrive more than once. Make the update idempotent on CheckoutRequestID.
In API responses (not the callback), ResponseCode "0" only means the request was accepted, not that the customer paid.
Sources: Laravel Daraja: error code reference · Kenzobe: M-Pesa Daraja API common errors
1037 DS timeout: user cannot be reached
Not known yetSeen in: CallbackRetry: Check the status before retryingConfirmed by several sources
Safaricom couldn't get an answer from the phone in time: it was off, had no signal, or the customer didn't respond.
How to fix it
- Don't mark it failed straight away. Query the transaction status (STK Push Query) before deciding.
- Don't let the customer start a second STK push until the first is resolved, or they may pay twice.
What to tell the customer
We couldn't reach your phone. Check you have signal, then we'll confirm your payment.
Developers report payments that completed minutes after a 1037. Treat it as "not yet known", not "failed".
Sources: Laravel Daraja: error code reference · Kenzobe: M-Pesa Daraja API common errors · Code With Karani: callbacks, idempotency and 1037
1001 Unable to lock subscriber
Not known yetSeen in: CallbackRetry: Later, it is temporaryConfirmed by several sources
The customer already has another M-Pesa transaction or USSD session in progress.
How to fix it
- Wait a minute and check the status before sending a new STK push.
What to tell the customer
You have another M-Pesa transaction in progress. Finish it, then try again.
Sources: Laravel Daraja: error code reference · Kenzobe: M-Pesa Daraja API common errors · Code With Karani: callbacks, idempotency and 1037
Failure reasons
Why a payment did not go through.
1 Insufficient balance
Not paidSeen in: CallbackRetry: Yes, as a new requestConfirmed by several sources
The customer's M-Pesa balance is too low for this payment.
How to fix it
- Mark the payment failed and let the customer retry after topping up, or offer Fuliza if your business supports it.
What to tell the customer
Your M-Pesa balance is too low. Top up and try again.
Sources: Kenzobe: M-Pesa Daraja API common errors · Code With Karani: callbacks, idempotency and 1037
1032 Request cancelled by user
Not paidSeen in: CallbackRetry: Yes, as a new requestConfirmed by several sources
The customer dismissed or cancelled the STK prompt on their phone.
How to fix it
- Mark the payment failed and show a "Try again" button that sends a new STK push.
What to tell the customer
You cancelled the payment on your phone. Tap "Try again" when you're ready.
Some sources say 1032 can also appear when the prompt times out without an answer.
Sources: Laravel Daraja: error code reference · Kenzobe: M-Pesa Daraja API common errors · Code With Karani: callbacks, idempotency and 1037
2001 Wrong PIN
Not paidSeen in: CallbackRetry: Yes, as a new requestConfirmed by several sources
The customer entered an incorrect M-Pesa PIN.
How to fix it
- Mark the payment failed and allow a retry. Repeated wrong PINs can lock the customer's M-Pesa account.
What to tell the customer
The M-Pesa PIN was incorrect. Please try again.
Sources: Laravel Daraja: error code reference · Kenzobe: M-Pesa Daraja API common errors · Code With Karani: callbacks, idempotency and 1037
1019 Transaction expired
Not paidSeen in: CallbackRetry: Yes, as a new requestConfirmed by several sources
The customer didn't complete the prompt before it expired.
How to fix it
- Mark the payment failed and allow a new STK push.
What to tell the customer
The payment request expired. Please try again.
Sources: Kenzobe: M-Pesa Daraja API common errors · Code With Karani: callbacks, idempotency and 1037
1025 Error sending the push request
Not paidSeen in: CallbackRetry: Later, it is temporaryReported by one source
Safaricom couldn't deliver the STK prompt, usually a network or Safaricom-side problem.
How to fix it
- Retry after a short wait. If it keeps happening, check Safaricom's service status.
Sources: Kenzobe: M-Pesa Daraja API common errors
17 Internal failure
Not paidSeen in: CallbackRetry: Later, it is temporaryReported by one source
A generic failure inside M-Pesa.
How to fix it
- Check the status, then retry once. Show a generic error if it fails again.
Sources: Kenzobe: M-Pesa Daraja API common errors
26 System busy
Not paidSeen in: CallbackRetry: Later, it is temporaryReported by one source
M-Pesa is under heavy load.
How to fix it
- Back off for about 30 seconds before retrying.
Sources: Kenzobe: M-Pesa Daraja API common errors
Request errors
Returned straight away when the API rejects your request.
400.002.02 Bad Request: invalid field
Request errorSeen in: API responseRetry: No, fix the cause firstReported by one source
A field in the request is missing or invalid.
How to fix it
- Check every field's name and format. Timestamp must be YYYYMMDDHHmmss.
- Send "Content-Type: application/json" and a JSON body.
- PhoneNumber and PartyA must be 2547XXXXXXXX or 2541XXXXXXXX.
Sources: Laravel Daraja: error code reference
400.002.05 Invalid request payload
Request errorSeen in: API responseRetry: No, fix the cause firstReported by one source
The JSON body doesn't match what the endpoint expects: a wrong field name or type.
How to fix it
- Compare field names and types with the Daraja docs for that endpoint. Amount must be a whole number.
Sources: Laravel Daraja: error code reference
404.001.03 Invalid Access Token
Request errorSeen in: API responseRetry: Yes, as a new requestConfirmed by several sources
The OAuth token is missing, expired (tokens last one hour), malformed, or from the other environment.
How to fix it
- Fetch a new token from /oauth/v1/generate?grant_type=client_credentials and cache it for under an hour.
- Send it as "Authorization: Bearer <token>", with exactly one space and no quotes.
- Don't mix sandbox credentials with production URLs, or the other way round.
Sources: Laravel Daraja: error code reference · Tailored Hosting: Daraja invalid access token · Kenzobe: M-Pesa Daraja API common errors
404.001.04 Invalid authentication header
Request errorSeen in: API responseRetry: No, fix the cause firstReported by one source
The Authorization header is wrong for this endpoint.
How to fix it
- Use Basic auth only for the OAuth endpoint, and Bearer for every other call. Use POST for every API except the OAuth one.
Sources: Laravel Daraja: error code reference
500.001.1001 Merchant does not exist / wrong credentials
Request errorSeen in: API responseRetry: Check the status before retryingConfirmed by several sources
This code is reused for several problems: a wrong BusinessShortCode or password, or (on STK Push Query) "the transaction is still being processed".
How to fix it
- Password must be base64(BusinessShortCode + Passkey + Timestamp), using the same Timestamp you send in the request. Decode yours with the Password decoder in the M-Pesa STK Push Builder.
- Check the passkey has no leading or trailing spaces and belongs to this shortcode.
- On STK Push Query, wait and query again. The payment may still be in progress.
The same code means different things on different endpoints. Read errorMessage before acting on it.
Sources: Laravel Daraja: error code reference · Code With Karani: 500.001.1001 · Code With Karani: callbacks, idempotency and 1037
500.002.1001 Duplicate OriginatorConversationID
Request errorSeen in: API responseRetry: Yes, as a new requestReported by one source
The OriginatorConversationID was already used.
How to fix it
- Generate a unique OriginatorConversationID (for example a UUID) for every B2C or similar request.
Sources: Laravel Daraja: error code reference
500.003.02 Spike arrest violation
Request errorSeen in: API responseRetry: Later, it is temporaryReported by one source
Too many requests per second.
How to fix it
- Throttle your requests, and cache the OAuth token instead of fetching one per call.
Sources: Laravel Daraja: error code reference
Request errorSeen in: API responseRetry: Later, it is temporaryReported by one source
Your app has used up its request quota.
How to fix it
- Reduce request volume, especially STK Push Query polling. Poll with backoff.
Sources: Laravel Daraja: error code reference
Handle every code in your code
Map each outcome code to one of four actions. Unknown codes fall back to "check the status", so a new or misspelled code never fulfils or cancels an order by accident.
// What to do with each M-Pesa Daraja (Safaricom) outcome code.
// Unknown codes fall back to "check_status": never fulfil or fail an order on a code you don't recognise.
const ACTIONS = {
"0": "fulfil", // Success
"1": "mark_failed", // Insufficient balance
"1032": "mark_failed", // Request cancelled by user
"1037": "check_status", // DS timeout: user cannot be reached
"2001": "mark_failed", // Wrong PIN
"1001": "check_status", // Unable to lock subscriber
"1019": "mark_failed", // Transaction expired
"1025": "mark_failed", // Error sending the push request
"17": "mark_failed", // Internal failure
"26": "mark_failed", // System busy
};
export function mpesaNextStep(code) {
const key = String(code ?? "").trim().toUpperCase().replace(/[\s-]+/g, "_");
return ACTIONS[key] ?? "check_status";
}
// Example:
// switch (mpesaNextStep(body.Body.stkCallback.ResultCode)) {
// case "fulfil": // verify amount, then mark the order paid (idempotently)
// case "mark_failed": // mark unpaid, show a retry button
// case "wait": // do nothing yet; a callback or your poller will follow
// case "check_status": // ask the status endpoint before deciding
// }
Frequently asked questions
What does M-Pesa ResultCode 1032 mean?
1032 (Request cancelled by user): The customer dismissed or cancelled the STK prompt on their phone. Mark the payment failed and show a "Try again" button that sends a new STK push.
What does M-Pesa ResultCode 1037 mean?
1037 (DS timeout: user cannot be reached): Safaricom couldn't get an answer from the phone in time: it was off, had no signal, or the customer didn't respond. Don't mark it failed straight away. Query the transaction status (STK Push Query) before deciding.
What does M-Pesa ResultCode 2001 mean?
2001 (Wrong PIN): The customer entered an incorrect M-Pesa PIN. Mark the payment failed and allow a retry. Repeated wrong PINs can lock the customer's M-Pesa account.
What does M-Pesa error 404.001.03 mean?
404.001.03 (Invalid Access Token): The OAuth token is missing, expired (tokens last one hour), malformed, or from the other environment. Fetch a new token from /oauth/v1/generate?grant_type=client_credentials and cache it for under an hour.
What does M-Pesa error 500.001.1001 mean?
500.001.1001 (Merchant does not exist / wrong credentials): This code is reused for several problems: a wrong BusinessShortCode or password, or (on STK Push Query) "the transaction is still being processed". Password must be base64(BusinessShortCode + Passkey + Timestamp), using the same Timestamp you send in the request. Decode yours with the Password decoder in the M-Pesa STK Push Builder.
Generate the requests
Build requests that avoid these errors: M-Pesa STK Push Builder and Password decoder, M-Pesa STK push in Node.js, M-Pesa STK push in Laravel, M-Pesa STK push in Django, M-Pesa callbacks and ResultCode 1037, handled right, M-Pesa B2C payouts in Node.js (B2C codes differ from STK).
Checked October 2026. Error codes change. If you see one that's missing or wrong, please let me know.