M-Pesa Ratiba
Create and manage M-PESA standing orders (recurring collections) from a digital channel.
Endpoint
https://sandbox.safaricom.co.ke/standingorder/v1/createStandingOrderExternalOverview
Programmatic creation and management of M-PESA standing orders for recurring collections: loan repayments, insurance premiums, subscription billing, SACCO contributions, and hire purchase installments are the use cases Safaricom lists.
This is a commercial API, billed per standing order executed: 5% of the transaction value, capped at KES 5 per execution, exclusive of VAT, charged to the organization consuming the API. Standard C2B transaction charges apply separately on the credited Pay Bill/Till, per its own tariff.
Onboarding
Create a sandbox app with the M-Ratiba product attached and test against the simulator. Once testing is complete, email apisupport@safaricom.co.ke for Go Live; Safaricom's team follows up for commercial discussion and contract signing before the API is enabled on your Pay Bill or Till.
How it works
- A user enters standing order details (name, amount, start date, end date) on the merchant's own digital channel.
- The merchant's system sends this to Daraja, which runs authentication.
- An M-PESA prompt is sent to the user, who enters their PIN, both consenting to the order and confirming ownership of the number.
- If the user isn't already opted in to standing orders, they're opted in automatically.
- The standing order is created.
- A callback is sent to the merchant with the status, transaction code, and a unique response ID.
Prerequisites
- Daraja account and sandbox app with the M-Ratiba product attached.
- Signed commercial agreement.
- Familiarity with REST, JSON, and asynchronous/callback-based processing.
Request
{
"StandingOrderName": "erick",
"ReceiverPartyIdentifierType": "2",
"TransactionType": "Standing Order Pay Bill External Third Party",
"BusinessShortCode": "300584",
"PartyA": "254708374149",
"Amount": "10",
"StartDate": "20241221",
"EndDate": "20241222",
"Frequency": "4",
"CustomStoId": "bae4ef32-3fb1-4998-988b-0063884a3d80",
"AccountReference": "Test",
"TransactionDesc": "Test",
"CallBackURL": "https://mydomain.com/mpesa-ratiba/result/"
}Defect in the source material
Safaricom's own sample request body writes this field as
"StandingOrderNameName", with the word "Name" duplicated, rather than
StandingOrderName as used in the parameter table on the same page.
StandingOrderName is what's used above and in the field reference below.
| Field | Description | Sample value |
|---|---|---|
StandingOrderName | Must be unique per customer; a customer cannot have two standing orders with the same name. | Phone Lipa Mdogo Mdogo |
StartDate / EndDate | yyyymmdd, defining when the order starts and stops executing. | 20240905 / 20250905 |
BusinessShortCode | The short code payments are sent to. | 1112223 |
TransactionType | Standing Order Pay Merchant External Third Party for a Buy Goods till, Standing Order Pay Bill External Third Party for a Pay Bill. | See left |
CustomStoId (documented elsewhere as CustomstdoId) | A UUID generated by the partner platform to trace the request; echoed back in the callback as ResponseRefId/RequestRefId. | bae4ef32-3fb1-4998-988b-0063884a3d80 |
Amount | Amount deducted each time the order executes. Whole numbers only. | 100 |
PartyA | The paying customer's M-PESA registered number, 2547XXXXXXXX. | 254712345678 |
CallBackURL | Result callback URL. | https://mydomain.com/callback |
AccountReference | Up to 12 characters. | Test |
TransactionDesc | Up to 13 characters. | Electric Bike Repayment |
Frequency | 1 One-off, 2 Daily, 3 Weekly, 4 Bi-weekly, 5 Monthly, 6 Bi-monthly, 7 Quarterly, 8 Half-yearly, 9 Yearly. | 2 |
ReceiverPartyIdentifierType | 2 for a Merchant Till (Till Number), 4 for a Business Short Code (Pay Bill). | 4 |
Defect in the source material
Safaricom's field reference itself names this parameter CustomStoId in the
request sample but CustomstdoId in the running text describing it, two
different spellings for what is presumably the same field. Both are reproduced
above rather than picking one, since which the live endpoint actually expects
hasn't been independently confirmed.
Response
{
"ResponseHeader": {
"responseRefID": "4dd9b5d9-d738-42ba-9326-2cc99e966000",
"responseCode": "200",
"responseDescription": "Request accepted for processing",
"ResultDesc": "The service request is processed successfully."
},
"ResponseBody": {
"responseDescription": "Request accepted for processing",
"responseCode": "200"
}
}| Field | Description |
|---|---|
responseRefID | Unique per-request identifier for tracing. |
responseCode | HTTP-style status: 200 success, 401 unauthorized, 500 system failure. |
ResultDesc / responseDescription | Status message. |
Callback payload
{
"responseHeader": {
"responseRefID": "06aae68f-7d5a-4b44-a22d-8aa77126689b",
"requestRefID": "06aae68f-7d5a-4b44-a22d-8aa77126689b",
"responseCode": "0",
"responseDescription": "Standing order created successfully"
},
"responseBody": {
"responseData": [
{ "name": "standingOrderName", "value": "mpesa_Ratiba_test_Name" },
{ "name": "amount", "value": "500.00" },
{ "name": "issuePaymentReminderUntil", "value": "20280407" },
{ "name": "reminderScheduleId", "value": "2571168" },
{ "name": "firstPaymentReminderDate", "value": "20260807" },
{ "name": "status", "value": "ACTIVE" },
{ "name": "TransactionID", "value": "2571168" },
{ "name": "ResponseCode", "value": "0" },
{ "name": "Status", "value": "OKAY" },
{ "name": "Msisdn", "value": "*********867" }
]
}
}Field name casing is inconsistent between the response and the callback in
Safaricom's own documentation: ResponseHeader/ResponseBody (capitalized)
in the initial response, responseHeader/responseBody (lowercase) in the
callback, and the callback's responseData array mixes lowercase (name,
value, status) and capitalized (TransactionID, ResponseCode, Status,
Msisdn) keys within the same array. All reproduced as documented.
Error codes
| Code | Cause | Notes |
|---|---|---|
1037 | STK Push prompt never reached the user | Outdated SIM, phone offline, or the SIM is too old to support STK updates. |
1025 | System error on the partner platform while sending the push | Retry; investigate the partner-side system. |
1032 | Request cancelled by the user | STK prompt timed out (1 to 3 minutes) or was actively declined. |
2001 | Initiator information invalid | Wrong M-PESA PIN entered. |
1001 | Unable to lock subscriber, transaction already in process | Conflicting USSD session; wait and retry, one push per user at a time. |
1050 | Duplicate standing order name | The customer already has a standing order with that exact name. |
1051 | Bad request | Invalid field(s) in the payload. |
Defect in the source material
Safaricom's error table lists 1037 twice, once as "DS timeout" (the push
never reaching the user) and again as the push reaching the user but the
response not returning in time. Both rows are reproduced as published.
Testing and Go Live
Daraja Simulator with the M-Ratiba product attached.
Postman: token from the sandbox OAuth endpoint, then call the endpoint above.
Going live requires the signed commercial agreement noted under Onboarding above, arranged via apisupport@safaricom.co.ke.
FAQs
Any registered company, according to Safaricom's documentation. A Pay Bill or Till is recommended but not mandatory, depending on the business model.
The deduction only succeeds if the customer has enough funds and has authorized the payment via their M-PESA PIN.
Ironic detail in the source material
Safaricom's own FAQ section for M-Pesa Ratiba closes with the line "These FAQs should help you understand and utilize the IMSI API effectively," referring to a different product entirely. It's Ratiba's FAQ page, not IMSI's; the sentence is reproduced here rather than silently corrected, as a flag that this section was evidently copy-pasted from the IMSI documentation.