// delivery & webhooks
Delivery & webhooks
EK SMS pushes a signed webhook the moment a message is delivered - no polling. Most providers in Nepal still make you poll for delivery; we don't.
Message statuses
| Status | Meaning |
|---|---|
| QUEUED | Accepted and charged; waiting for the send worker. |
| SENT | Handed to the upstream network; awaiting a delivery receipt. |
| DELIVERED | Confirmed delivered to the handset. |
| UNDELIVERED | The network reported non-delivery. |
| FAILED | Rejected upstream or failed after retries; credits refunded. |
The webhook
When you set callbackUrl on a send, EK SMS POSTs a JSON payload to it on every delivery-status change:
POST https://your-app.com/webhooks/sms
X-EKSMS-Signature: 9f86d081884c7d659a2feaa0c55ad015...
X-EKSMS-Event: message.delivered
Content-Type: application/json
{
"event": "message.delivered",
"messageId": "a1b2c3d4-...",
"recipient": "9818000000",
"status": "DELIVERED",
"deliveredAt": "2026-06-20T05:51:00.000Z"
}
Verify the signature
Every webhook carries an X-EKSMS-Signature header - an HMAC-SHA256 of the raw request body, keyed with your webhook signing secret. Verify it before trusting the payload, using a constant-time compare:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(rawBody, signature, secret) {
const expected = createHmac("sha256", secret)
.update(rawBody) // the RAW request body, not re-serialized
.digest("hex");
return timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}
// Express: use express.raw() so you get the exact bytes we signed.
app.post("/webhooks/sms", express.raw({ type: "*/*" }), (req, res) => {
const ok = verify(req.body, req.get("X-EKSMS-Signature"), process.env.EKSMS_WEBHOOK_SECRET);
if (!ok) return res.status(401).end();
const event = JSON.parse(req.body.toString());
// ... handle event.status
res.status(200).end();
});
Sign over the raw bytes
Compute the HMAC over the exact raw body we sent, before any JSON re-serialization. Re-stringifying the parsed object can change whitespace and break the signature.
Respond fast, then process
Return
2xx quickly to acknowledge receipt. Do slow work asynchronously - we retry a few times on non-2xx with backoff, so make your handler idempotent.Next: Errors · API reference