Appearance
Delivery & webhooks
Message statuses
| Status | Meaning |
|---|---|
queued | Accepted and debited, waiting for the carrier. |
sent | The carrier accepted it. Messages with no delivery receipt stay here. |
delivered | The carrier confirmed delivery to the handset. |
undelivered | The carrier reported it could not deliver (phone off too long, number inactive). Not refunded. |
failed | The carrier rejected it, or every retry failed. Refunded automatically. |
Delivery receipts depend on the destination network. Where the carrier sends none, sent is the final status.
Refunds
A failed message refunds its exact cost to your wallet on its own, stamped on the message and visible in your Transactions ledger. sent and delivered messages are never refunded. undelivered is not refunded either: the carrier did the work.
Webhooks
Set a webhook URL on the API keys page and we POST these events to it:
| Event | When |
|---|---|
message.delivered | A delivery receipt confirmed the handset got it. |
message.failed | A message failed terminally (and was refunded). |
batch.completed | Every message in a batch reached an outcome. |
balance.low | A send left your balance under the low-balance threshold. |
Example delivery:
json
{
"event": "message.delivered",
"created_at": "2026-08-30T17:02:11+00:00",
"message_id": "b3e6a9d0-...",
"batch_id": "9d2f7c1e-...",
"to": "+263771234567"
}Failed deliveries retry five times with growing gaps (30s up to 8 minutes). Answer with any 2xx status to acknowledge.
Verifying signatures
Every webhook is signed so you can prove it came from us. Two headers arrive with each POST:
X-XashSms-Timestamp: 1793552531
X-XashSms-Signature: 3f1d...The signature is HMAC-SHA256 over "{timestamp}.{raw body}" using your whsec_ secret from the API keys page. Verify before trusting:
php
$expected = hash_hmac(
'sha256',
$request->header('X-XashSms-Timestamp').'.'.$request->getContent(),
$webhookSecret,
);
abort_unless(hash_equals($expected, $request->header('X-XashSms-Signature')), 401);Reject anything with a bad signature or a timestamp older than a few minutes. Rotating the secret on the dashboard invalidates the old one immediately.