Webhooks
Receive real-time notifications when message status changes. Configure webhook URLs to get delivery confirmations, failures, and other events pushed to your server.
How Webhooks Work
Configure your URL
Set a webhook URL in your API request or client settings.
We send a POST request
When an event occurs (delivery, failure, etc.), we POST JSON to your URL.
Your server responds
Return HTTP 200 within 5 seconds to acknowledge receipt.
Retry on failure
If your server doesn't respond with 200, we retry up to 5 times with exponential backoff.
Webhook Payload
All webhook payloads are sent as JSON via HTTP POST. The payload includes:
{
"event": "sms.delivered",
"timestamp": "2026-01-13T10:30:15Z",
"data": {
"message_id": "msg_abc123xyz789",
"recipient": "+265888123456",
"status": "delivered",
"sent_at": "2026-01-13T10:30:00Z",
"delivered_at": "2026-01-13T10:30:15Z",
"cost": 0.05,
"currency": "MWK",
"reference": "txn_12345",
"client_id": "client_abc123"
}
}Event Types
| Event | Description |
|---|---|
sms.sent | Message sent to carrier |
sms.delivered | Message delivered to recipient |
sms.failed | Delivery failed |
sms.expired | Delivery timeout |
whatsapp.sent | WhatsApp message sent |
whatsapp.delivered | WhatsApp message delivered |
whatsapp.read | WhatsApp message read |
whatsapp.failed | WhatsApp delivery failed |
Setting Up Webhooks
Option 1: Per-Request Webhook
Pass the webhook_url parameter when sending a message:
curl -X POST "https://api.yourdomain.com/api/v1/sms/send" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+265888123456",
"message": "Hello!",
"webhook_url": "https://yourserver.com/webhooks/sms"
}'Option 2: Account-Level Webhook
Configure a default webhook URL in your client settings. All messages will trigger callbacks to this URL.
Verifying Webhook Signatures
Each webhook includes an X-Webhook-Signature header to verify the request is authentic.
import hmac
import hashlib
def verify_webhook(payload_body, signature_header, webhook_secret):
"""Verify the webhook signature"""
expected = hmac.new(
webhook_secret.encode("utf-8"),
payload_body,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature_header)
# In your Flask/FastAPI handler:
@app.post("/webhooks/sms")
async def handle_webhook(request: Request):
body = await request.body()
signature = request.headers.get("X-Webhook-Signature", "")
if not verify_webhook(body, signature, WEBHOOK_SECRET):
return {"error": "Invalid signature"}, 401
event = await request.json()
print(f"Received event: {event['event']}")
return {"status": "ok"}Retry Policy
| Attempt | Delay |
|---|---|
| 1st retry | 30 seconds |
| 2nd retry | 2 minutes |
| 3rd retry | 10 minutes |
| 4th retry | 1 hour |
| 5th retry | 6 hours |
After 5 failed attempts, the webhook is marked as failed and an alert is logged. You can view failed webhooks in the admin dashboard under Activity Logs.