Delivery webhooks
Get a POST to your server each time a message is delivered or fails.
A webhook tells your server what happened to a message, so you do not have to keep asking.
Set your webhook URL
In the dashboard open Developers then Webhooks, or call the API:
PUT https://api.smsyanu.com/v1/webhooks
Content-Type: application/json
{ "dlr_url": "https://example.mw/sms/dlr", "is_active": true }- The URL must be public HTTPS. Private and local addresses are refused.
- An empty string clears the URL.
GET /v1/webhooksshows the current settings and your signing secret.POST /v1/webhooks/testsends a test event.POST /v1/webhooks/rotate-secretmakes a new signing secret. Update your server before you rotate.
What we send
We POST JSON when a message reaches a final status:
{
"event": "message.delivered",
"message_id": "6f1c2b9e-1d2a-4c55-9a0e-2b7f0c1d3e4f",
"status": "delivered",
"phone": "+265990000000",
"sender_id": "YANU",
"segments": 1,
"encoding": "GSM-7",
"livemode": true,
"cost": 20.0,
"cost_currency": "MWK",
"error_code": null,
"error_message": null,
"retry_count": 0,
"delivered_at": "2026-11-05T08:30:06+00:00",
"failed_at": null
}Headers:
| Header | Meaning |
|---|---|
X-Webhook-ID | Unique id for this event. Use it to skip duplicates. |
X-Webhook-Signature | sha256= then the hex HMAC-SHA256 of the raw body, keyed with your signing secret. |
Check the signature
Always check the signature before you trust the body. Compare in constant time.
import hashlib, hmac
def is_valid(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")import crypto from 'node:crypto';
export function isValid(rawBody, header, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header || '');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}<?php
function is_valid(string $rawBody, string $header, string $secret): bool {
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $header);
}Answer quickly
- Reply with any
2xxstatus within 15 seconds. Do slow work after you reply. - If your server fails, we retry twice, after about 5 and 15 seconds. After that the event stays in your webhook log in the dashboard, where you can send it again.
- The same event can arrive more than once. Use
X-Webhook-IDormessage_idto ignore repeats.
To be confirmed
The payload fields, retry timing and dashboard menu names come from the current code and may change before launch.