-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patherrors.json
More file actions
270 lines (270 loc) · 14.5 KB
/
errors.json
File metadata and controls
270 lines (270 loc) · 14.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
[
{
"code": "1037",
"title": "MSISDN Unreachable / No Response From User",
"api": "STK Push",
"category": "callback",
"description": "The STK prompt was sent but M-Pesa received no response from the user's handset within the timeout window.",
"causes": [
"User took too long to enter their PIN",
"Phone is off or out of network coverage",
"Outdated SIM card",
"Very common with iOS eSIMs"
],
"fix": "Prompt the user to check their phone and retry. If the issue persists, ask them to dial *234# to update their M-Pesa menu, then retry.",
"notes": "This is the most Googled Daraja error. Do not treat it as fatal — always offer a retry flow in your UI.",
"related": [
"1032",
"1019",
"1025"
]
},
{
"code": "1032",
"title": "Request Cancelled by User",
"api": "STK Push",
"category": "callback",
"description": "The user deliberately or accidentally dismissed the STK/USSD payment prompt.",
"causes": [
"User pressed Cancel on the STK prompt",
"User ignored the prompt until it auto-dismissed"
],
"fix": "Re-initiate the STK push request. No action needed on the backend — this is a user-side cancellation.",
"related": [
"1037",
"1019",
"1001"
]
},
{
"code": "1",
"title": "Insufficient Funds",
"api": "STK Push",
"category": "callback",
"description": "The user has neither enough M-Pesa balance nor an available Fuliza overdraft limit to complete the transaction.",
"causes": [
"M-Pesa wallet balance too low",
"Fuliza limit exhausted or not activated"
],
"fix": "Inform the user they need to top up their M-Pesa account and retry.",
"related": [
"1032",
"1037"
]
},
{
"code": "1001",
"title": "Transaction In Progress",
"api": "STK Push",
"category": "callback",
"description": "M-Pesa cannot lock the subscriber because they already have an active USSD session processing another transaction.",
"causes": [
"User has another M-Pesa transaction in progress",
"Previous STK push not yet resolved"
],
"fix": "Wait 1–2 minutes and retry the payment request.",
"related": [
"1037",
"1032"
]
},
{
"code": "1025",
"title": "Unable to Send STK Prompt",
"api": "STK Push",
"category": "request",
"description": "A temporary system issue prevented the STK prompt from being sent, or the TransactionDesc parameter exceeded the character limit.",
"causes": [
"TransactionDesc field exceeds 182 characters including spaces",
"Temporary M-Pesa system issue"
],
"fix": "Check that your TransactionDesc parameter is under 182 characters. If it is, wait 1–2 minutes and retry.",
"notes": "Error code 9999 is the same error and should be handled identically.",
"related": [
"9999",
"1037"
]
},
{
"code": "9999",
"title": "Unable to Send STK Prompt (System Error)",
"api": "STK Push",
"category": "request",
"description": "Alias for error 1025. A system-level failure prevented the STK prompt from being delivered.",
"causes": [
"Temporary M-Pesa platform instability",
"TransactionDesc over 182 characters"
],
"fix": "Same fix as error 1025 — check TransactionDesc length, then retry after 1–2 minutes.",
"notes": "Treat 9999 and 1025 identically in your error handling logic.",
"related": [
"1025",
"1037"
]
},
{
"code": "Invalid Initiator Info",
"title": "Wrong M-Pesa PIN Entered",
"api": "STK Push",
"category": "callback",
"description": "The user entered an incorrect M-Pesa PIN when responding to the STK prompt.",
"causes": [
"User typed the wrong PIN",
"User has recently changed their PIN and used the old one"
],
"fix": "Prompt the user to retry and enter their correct M-Pesa PIN. Note: M-Pesa allows a maximum of 3 failed PIN attempts.",
"related": [
"1037",
"1032"
]
},
{
"code": "400.002.02",
"title": "Bad Request — Invalid Request Parameter",
"api": "HTTP",
"category": "request",
"description": "The API rejected the request because a field in the payload failed validation. The errorMessage suffix tells you which field is invalid (e.g. 'Bad Request - Invalid BusinessShortCode', 'Bad Request - Invalid Timestamp', 'Bad Request - Invalid Amount').",
"causes": [
"Invalid BusinessShortCode: In Python, sending the payload using data=payload instead of json=payload causes the shortcode to be unparseable",
"Invalid BusinessShortCode: Shortcode sent as wrong type (string vs integer) or incorrect format",
"Invalid Timestamp: Timestamp field is in the wrong format — must be YYYYMMDDHHmmss (e.g. 20231207103045), not ISO 8601 or any other format",
"Invalid Amount: Amount is zero, negative, non-numeric, or formatted with decimal places when an integer is expected"
],
"fix": "Read the full errorMessage field — the suffix after 'Bad Request - ' tells you exactly which field to fix. For BusinessShortCode: in Python requests change data=payload to json=payload and ensure Content-Type is application/json. For Timestamp: use the exact format YYYYMMDDHHmmss with no separators. For Amount: send a positive integer with no decimals.",
"notes": "This code is a generic Daraja request validation error. The errorMessage changes based on which field is invalid — always log the full errorMessage, not just the code.",
"related": [
"500.001.1001",
"401.003.01"
]
},
{
"code": "Invalid Access Token (Post Go-Live)",
"title": "Access Token Invalid After Going Live",
"api": "Go-Live",
"category": "auth",
"description": "After switching to production credentials, the access token is rejected even though the same code worked in sandbox.",
"causes": [
"Daraja app's API product not yet approved for live access",
"Still using sandbox base URL instead of https://api.safaricom.co.ke/",
"Consumer key and secret not updated to live credentials",
"Passkey not updated to the live passkey received via email"
],
"fix": "1) Verify all four values are live: Consumer Key, Consumer Secret, Passkey, and base URL. 2) If all four are correct and it still fails, email apisupport@safaricom.co.ke with subject 'Invalid Access Token Error After Going Live', and include your Daraja app ID and shortcode. The issue is usually that Safaricom hasn't finished internal approval.",
"notes": "This is not a code issue 90% of the time. Do not spend hours debugging — escalate to Safaricom support early.",
"related": [
"404.001.03",
"401.003.01"
]
},
{
"code": "C2B Sandbox Callbacks Unreliable",
"title": "Confirmation/Validation Callbacks Not Firing in Sandbox",
"api": "C2B",
"category": "callback",
"description": "C2B sandbox callbacks either never arrive or arrive only ~40% of the time, making it impossible to fully test the integration locally.",
"causes": [
"Known Safaricom sandbox instability for C2B callbacks",
"Callback URL not publicly accessible (localhost URLs are blocked)",
"Using ngrok on a free tier (can be blocked by Safaricom)"
],
"fix": "For C2B specifically, the developer community consensus is to test against a live deployment rather than sandbox. Deploy to a VPS or platform like Railway, register your real HTTPS URL, and test with small live transactions.",
"notes": "This is a known, long-standing Daraja sandbox limitation, not a bug in your code. Safaricom does not allow ngrok on production callback URLs.",
"related": [
"1037",
"1032"
]
},
{
"code": "2001",
"title": "Invalid Initiator Information (B2C / B2B)",
"api": "B2C",
"category": "auth",
"description": "The API operator credentials sent in a B2C or B2B request are invalid. This is a developer-side credential error, not a user PIN error.",
"causes": [
"InitiatorName does not match what is registered on the Daraja portal",
"SecurityCredential was encrypted using the wrong certificate (e.g. old G2 certificate instead of the Daraja certificate)",
"SecurityCredential was encrypted using sandbox certificate in production or vice versa",
"Initiator does not belong to the shortcode being used"
],
"fix": "Download a fresh copy of the public key certificate from your Daraja portal (not from a GitHub repo — those are often outdated G2 certificates). Re-encrypt your initiator password using that certificate and update your SecurityCredential value. Also verify the InitiatorName matches exactly what is on the Daraja portal under your app credentials.",
"notes": "Do not confuse this with the STK Push 'Invalid Initiator Info' entry — that one is about a user entering the wrong PIN. This error code 2001 appears in B2C and B2B result callbacks and is entirely a developer credentials issue.",
"related": [
"500.001.1001",
"400.002.02"
]
},
{
"code": "1019",
"title": "Transaction Expired",
"api": "STK Push",
"category": "callback",
"description": "The STK Push transaction exceeded the allowable processing time before the user responded.",
"causes": [
"User did not respond to the STK prompt before it timed out (typically 1–3 minutes depending on phone model)",
"User's phone was slow to display the prompt due to network lag"
],
"fix": "Inform the user their payment request expired and offer them a button to initiate a fresh STK push. Implement a polling loop using the STK Push Query API (stkpushquery endpoint) to catch this state proactively before relying solely on the callback.",
"notes": "Different from error 1037 — 1037 means the prompt never reached the phone. 1019 means the prompt arrived but the user did not act on it in time.",
"related": [
"1037",
"1032"
]
},
{
"code": "500.001.1001",
"title": "Wrong Credentials / MerchantValidate Failed",
"api": "STK Push",
"category": "request",
"description": "The STK Push request was rejected before an STK prompt was sent. This code covers two distinct scenarios: (1) Wrong credentials — the Password field failed validation, and (2) Merchant does not exist — the shortcode is not registered on the platform.",
"causes": [
"Wrong credentials: The Timestamp value used to build the Password does not match the Timestamp field sent in the request body — they must be identical",
"Wrong credentials: Timestamp is in the wrong format (must be YYYYMMDDHHmmss, e.g. 20240315143022)",
"Wrong credentials: Wrong passkey used — sandbox passkey used in production or vice versa",
"Wrong credentials: Password was not base64-encoded correctly (must be base64 of shortcode + passkey + timestamp concatenated as a plain string)",
"Merchant does not exist: The BusinessShortCode is not registered or activated on the Daraja platform for the current environment"
],
"fix": "Check the full errorMessage field first. If it says '[MerchantValidate] - Wrong credentials': generate the Timestamp once, store it in a variable, and use that exact same variable both to build the Password and as the Timestamp field — never generate it twice. Verify your passkey matches the environment. If it says 'Merchant does not exist': confirm your shortcode is correctly registered and that you are not mixing sandbox and production shortcodes.",
"notes": "This error appears in the errorCode field of the immediate API response (not in a callback). The errorMessage will be either '[MerchantValidate] - Wrong credentials' or 'Merchant does not exist' — these are two different root causes under the same code. Confirmed in official Safaricom GitHub issue trackers (LNMOnlineAndroidSample issues #8 and #50).",
"related": [
"400.002.02",
"404.001.03"
]
},
{
"code": "401.003.01",
"title": "Invalid Access Token (OAuth Step)",
"api": "HTTP",
"category": "auth",
"description": "The access token is invalid during the OAuth authorization step itself. Different from 404.001.03, which occurs when a valid-looking but expired token is used on an API call — this one fires when the token is rejected at the authorization layer.",
"causes": [
"Token was generated in sandbox but used against the production OAuth endpoint or vice versa",
"Token value is malformed or truncated (e.g. copy-paste error)",
"Token was revoked or the Daraja app credentials were rotated after the token was issued"
],
"fix": "Regenerate a fresh access token using the correct Consumer Key and Secret for the target environment (sandbox vs production). Ensure you are calling the correct base URL: https://sandbox.safaricom.co.ke for sandbox and https://api.safaricom.co.ke for production. Log the raw token value to confirm it is not truncated.",
"notes": "Confirmed in official Safaricom GitHub issue thread (LNMOnlineAndroidSample#11). Distinct from 404.001.03 — both relate to token problems, but 401.003.01 is an invalid token during authorization and 404.001.03 is an expired token during API calls. Handle both identically in code: regenerate and retry.",
"related": [
"404.001.03",
"500.001.1001"
]
},
{
"code": "404.001.03",
"title": "Access Token Expired or Missing",
"api": "HTTP",
"category": "auth",
"description": "The Bearer token in the Authorization header is invalid, expired, or absent. This causes all API calls to fail with a 404 response.",
"causes": [
"Access token has expired — tokens are valid for exactly 3600 seconds (1 hour)",
"Token was never refreshed between requests",
"Authorization header is missing the word 'Bearer ' before the token",
"Token was generated in sandbox but used against the production endpoint or vice versa"
],
"fix": "Implement token caching with automatic refresh: store the token and its expiry time, and regenerate it before it expires rather than on every request. A safe pattern is to refresh the token when it is within 5 minutes of expiry. Ensure your Authorization header is formatted exactly as: 'Bearer <token>' with a space between Bearer and the token value.",
"notes": "This is different from the Go-Live access token issue in this list. That one happens because Safaricom has not yet internally approved the live app. This one (404.001.03) happens during normal operation when token lifecycle is not managed correctly.",
"related": [
"401.003.01",
"500.001.1001"
]
}
]