Webhook Events
This page lists every event type AfriRoute can deliver to your webhook endpoint, along with example payloads. All events share the common envelope: id, event, created_at, and data.
📋 Event Catalog​
| Event | Trigger |
|---|---|
sms.delivered | SMS confirmed delivered by the carrier |
sms.failed | SMS could not be delivered |
payment.completed | A charge succeeded |
payment.failed | A charge failed or was declined |
payout.completed | A payout/disbursement settled |
voice.completed | A voice call ended normally |
voice.failed | A voice call failed to connect |
verification.completed | An identity verification finished successfully |
verification.failed | An identity verification was rejected |
📨 SMS Events​
{
"id": "evt_sms_001",
"event": "sms.delivered",
"created_at": "2026-05-10T10:30:05Z",
"data": {
"message_id": "msg_abc123",
"to": "+251911234567",
"status": "delivered",
"segments": 1,
"delivered_at": "2026-05-10T10:30:05Z"
}
}
{
"id": "evt_sms_002",
"event": "sms.failed",
"created_at": "2026-05-10T10:30:09Z",
"data": {
"message_id": "msg_def456",
"to": "+251911234567",
"status": "failed",
"error_code": "UNDELIVERABLE",
"error": "Number unreachable"
}
}
💰 Payment Events​
{
"id": "evt_pay_001",
"event": "payment.completed",
"created_at": "2026-05-10T11:02:00Z",
"data": {
"transaction_id": "txn_7g8h9",
"amount": 1500,
"currency": "KES",
"method": "mpesa",
"status": "completed",
"completed_at": "2026-05-10T11:02:00Z"
}
}
{
"id": "evt_pay_002",
"event": "payment.failed",
"created_at": "2026-05-10T11:05:00Z",
"data": {
"transaction_id": "txn_1a2b3",
"amount": 1500,
"currency": "KES",
"status": "failed",
"error_code": "INSUFFICIENT_FUNDS"
}
}
📞 Voice Events​
{
"id": "evt_voice_001",
"event": "voice.completed",
"created_at": "2026-05-10T12:00:30Z",
"data": {
"call_id": "call_xy12",
"to": "+254712345678",
"duration_seconds": 73,
"status": "completed"
}
}
🪪 Identity Events​
{
"id": "evt_id_001",
"event": "verification.completed",
"created_at": "2026-05-10T14:30:45Z",
"data": {
"verification_id": "ver_abc123xyz",
"status": "verified",
"results": {
"document_authentic": true,
"face_match": true,
"liveness_passed": true,
"overall_risk": "low"
}
}
}
{
"id": "evt_id_002",
"event": "verification.failed",
"created_at": "2026-05-10T14:31:10Z",
"data": {
"verification_id": "ver_zzz999",
"status": "rejected",
"failure_reasons": ["face_mismatch", "liveness_failed"]
}
}
🧠Routing Events (Node.js)​
const handlers = {
'sms.delivered': (d) => updateMessage(d.message_id, 'delivered'),
'sms.failed': (d) => updateMessage(d.message_id, 'failed'),
'payment.completed': (d) => fulfillOrder(d.transaction_id),
'verification.completed': (d) => approveCustomer(d.verification_id)
};
app.post('/webhook', (req, res) => {
const { event, data } = req.body; // verify signature first
(handlers[event] || (() => console.log('unhandled', event)))(data);
res.sendStatus(200);
});
💡 Best Practices​
- Switch on
eventand default-handle unknown types so new events don't break your endpoint. - Treat
dataas additive — AfriRoute may add fields without a version bump. - Key idempotency on
id, not on resource IDs that can repeat across events. - Subscribe narrowly in configuration to only the events you process.
📚 Related Resources​
Last Updated: May 2026 | Need help? [email protected]