Delivery reports

Two ways to know a message reached the handset: push webhooks to your endpoint (recommended) or the lookup endpoint.

Push: DLR webhooks

Register a callback URL once (see POST /v2/companies/) and we POST each delivery status to it as networks confirm:

Webhook payload (POST to your URL)
{
  "trackingId": "order-4521-confirm",
  "phone": "254712345678",
  "status": "Delivered",
  "deliveredAt": "2026-07-11T09:41:22+03:00"
}

Status values

FieldTypeRequiredDescription
DeliveredfinalNoConfirmed on the handset.
FailedfinalNoThe network could not deliver (off, invalid, barred).
RejectedfinalNoThe carrier refused the message (filtering, invalid sender for that route).
QueuedinterimNoDispatched, awaiting carrier confirmation.

Why a message failed

A failed message carries the carrier's own reason alongside the status, in plain language rather than an operator code: absent subscriber, invalid number, handset memory full, sender blocked for this subscriber. It is on the message in the portal, in the API response, and in a message export, so “why did these 40 fail” is answerable without a support ticket.

Note that a hashed subscriber number can appear in place of the MSISDN on some Safaricom traffic. Use phone lookup to tie it back to a customer.

Webhook hygiene

Respond 200 quickly (under 5s) and process asynchronously. Treat deliveries as at-least-once. Dedupe on trackingId + status. Your endpoint must be HTTPS.

Pull: the lookup endpoint

POSThttps://api.mobilesasa.com/v1/dlr

Parameters

FieldTypeRequiredDescription
messageIdstringYesThe message reference to look up. Your v2 trackingId, or the ID returned at send time.
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

curl -X POST https://api.mobilesasa.com/v1/dlr \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messageId": "order-4521-confirm"
  }'
Response
{
  "status": true,
  "responseCode": "0200",
  "message": "Delivered"
}

Pull with filters: the v2 lookup

POSThttps://api.mobilesasa.com/v2/dlr

The v2 lookup returns a paginated list instead of a single status, and filters by phone, sender and date range. start and end are required — omitting them answers The start is not a valid date.

Parameters

FieldTypeRequiredDescription
startstringYesWindow start. YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, or RFC3339.
endstringYesWindow end, same formats. A bare date means end of that day.
messageIdstringNoYour v2 trackingId, or the ID returned at send time.
phonestringNoFilter to one recipient.
senderIDstringNoFilter to one sender name.
pageintNoPage number, 1-based.
# Paste your token once (it starts with mbs_):
export MOBILESASA_TOKEN="mbs_your_token_here"

curl -X POST https://api.mobilesasa.com/v2/dlr \
  -H "Authorization: Bearer $MOBILESASA_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "start": "2026-08-01",
    "end": "2026-08-01",
    "phone": "254712345678",
    "page": 1
  }'

Which should I use?

Webhooks, always, if you can host an endpoint. They're real-time and free of polling overhead. The lookup exists for spot checks, reconciliation jobs, and platforms that can't receive inbound HTTP.