Skip to Content
GuidesDelivery & status

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

StatusWhat it meansFinal?
queuedAccepted by us, on its way to the carrierNo
sentThe carrier accepted itNo
deliveredIt reached the handsetYes
failedIt didn’t get there. provider_status says whyYes
skippedA merge tag had no value for this contact, so nothing was sentYes

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, failed and skipped never 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:

CauseWhat to do
Handset off or out of coverageNothing — the carrier retries for hours
Number no longer in serviceStop sending to it; mark it in your own records
Sender ID not permitted on that networkSee Sender IDs. The most common cause of “sent but never arrived”
Recipient opted outThey 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.

Last updated on