Delivery & status
A 202 from /sms/send means we accepted your message and charged for it.
Whether it reached the handset is a separate question, answered here.
The statuses
| Status | What it means | Final? |
|---|---|---|
queued | Accepted by us, on its way to the carrier | No |
sent | The carrier accepted it | No |
delivered | It reached the handset | Yes |
failed | It didn’t get there. provider_status says why | Yes |
skipped | A merge tag had no value for this contact, so nothing was sent | Yes |
Most messages settle within seconds. A handset that’s off or out of coverage
can stay sent for hours while the carrier retries, so treat anything that
isn’t delivered or failed as still in progress rather than lost.
skipped only happens on campaigns using merge tags. If a contact has no
value for {amount}, we’d rather skip them than send them a message reading
“Your balance is {amount}”. Fix the contact and retry just those from the
campaign page.
Checking a message
Everything recently sent
curl "https://api.wakalisms.com/sms/messages?page=1&page_size=50" -H "X-API-Key: YOUR_KEY"{
"data": [
{
"id": "0f2c...",
"phone": "+254712345678",
"status": "delivered",
"provider_status": "DeliveredToTerminal",
"carrier": "safaricom",
"source": "api",
"delivered_at": "2026-10-26T09:14:22Z",
"created_at": "2026-10-26T09:14:18Z"
}
],
"total": 1, "page": 1, "pages": 1
}Filter to one recipient with ?phone=+254712345678.
One message, checked live
If you kept the provider id, this asks the carrier right now rather than returning what we last stored:
curl https://api.wakalisms.com/sms/delivery/PROVIDER_MESSAGE_ID -H "X-API-Key: YOUR_KEY"Polling sensibly
There are no outbound webhooks yet — see below — so production integrations poll. Done carelessly that’s a lot of wasted requests; this pattern isn’t:
// Check a batch on a backoff, and stop as soon as everything has settled.
const FINAL = new Set(["delivered", "failed", "skipped"]);
async function waitForDelivery(phone, { attempts = 6 } = {}) {
let wait = 5000;
for (let i = 0; i < attempts; i++) {
await new Promise(r => setTimeout(r, wait));
const res = await fetch(
`https://api.wakalisms.com/sms/messages?phone=${encodeURIComponent(phone)}&page_size=1`,
{ headers: { "X-API-Key": process.env.WAKALI_API_KEY } },
);
const { data } = await res.json();
const message = data[0];
if (message && FINAL.has(message.status)) return message;
wait = Math.min(wait * 2, 120000); // 5s, 10s, 20s ... capped at 2 min
}
return null; // still in flight; it will settle on its own
}Three things make the difference:
- Back off. Doubling the gap turns dozens of requests into a handful.
- Stop at a final status.
delivered,failedandskippednever change. - Poll the list, not each message. One request can cover a whole batch;
/sms/delivery/{id}calls the carrier every time, so keep it for one-offs.
Don’t block a user’s checkout or booking on a delivered status. Send the
SMS, confirm the booking, and reconcile delivery in the background. Carrier
retries can take minutes, and that’s normal rather than a failure.
When delivery fails
provider_status carries the carrier’s own wording. The usual causes:
| Cause | What to do |
|---|---|
| Handset off or out of coverage | Nothing — the carrier retries for hours |
| Number no longer in service | Stop sending to it; mark it in your own records |
| Sender ID not permitted on that network | See Sender IDs. The most common cause of “sent but never arrived” |
| Recipient opted out | They replied STOP. We exclude them automatically |
A failed message can be sent again as a new, separately billed message:
curl -X POST https://api.wakalisms.com/sms/messages/MESSAGE_ID/retry -H "X-API-Key: YOUR_KEY"Already-delivered messages can’t be retried, and one that’s still sent has
to be a few minutes old first, so a slow carrier isn’t paid for twice.
What about webhooks?
We don’t push delivery receipts to your server yet. You poll, using the pattern above.
If push delivery receipts would matter for your integration, tell us at support@wakalisms.com. It’s on the roadmap and knowing who needs it moves it up.
The /webhooks/delivery endpoint you may see in the API explorer is how our
upstream carrier notifies us. It isn’t something you can point at your own
server.