Daraja SDK Logomark
Daraja SDK
Telco & Identity API

M-Pesa Ratiba

Create and manage M-PESA standing orders (recurring collections) from a digital channel.

Endpoint

https://sandbox.safaricom.co.ke/standingorder/v1/createStandingOrderExternal

Overview

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

  1. A user enters standing order details (name, amount, start date, end date) on the merchant's own digital channel.
  2. The merchant's system sends this to Daraja, which runs authentication.
  3. An M-PESA prompt is sent to the user, who enters their PIN, both consenting to the order and confirming ownership of the number.
  4. If the user isn't already opted in to standing orders, they're opted in automatically.
  5. The standing order is created.
  6. 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.

FieldDescriptionSample value
StandingOrderNameMust be unique per customer; a customer cannot have two standing orders with the same name.Phone Lipa Mdogo Mdogo
StartDate / EndDateyyyymmdd, defining when the order starts and stops executing.20240905 / 20250905
BusinessShortCodeThe short code payments are sent to.1112223
TransactionTypeStanding 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
AmountAmount deducted each time the order executes. Whole numbers only.100
PartyAThe paying customer's M-PESA registered number, 2547XXXXXXXX.254712345678
CallBackURLResult callback URL.https://mydomain.com/callback
AccountReferenceUp to 12 characters.Test
TransactionDescUp to 13 characters.Electric Bike Repayment
Frequency1 One-off, 2 Daily, 3 Weekly, 4 Bi-weekly, 5 Monthly, 6 Bi-monthly, 7 Quarterly, 8 Half-yearly, 9 Yearly.2
ReceiverPartyIdentifierType2 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"
  }
}
FieldDescription
responseRefIDUnique per-request identifier for tracing.
responseCodeHTTP-style status: 200 success, 401 unauthorized, 500 system failure.
ResultDesc / responseDescriptionStatus 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

CodeCauseNotes
1037STK Push prompt never reached the userOutdated SIM, phone offline, or the SIM is too old to support STK updates.
1025System error on the partner platform while sending the pushRetry; investigate the partner-side system.
1032Request cancelled by the userSTK prompt timed out (1 to 3 minutes) or was actively declined.
2001Initiator information invalidWrong M-PESA PIN entered.
1001Unable to lock subscriber, transaction already in processConflicting USSD session; wait and retry, one push per user at a time.
1050Duplicate standing order nameThe customer already has a standing order with that exact name.
1051Bad requestInvalid 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

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.

On this page