Skip to main content

Delivery Reports

Track the real-time delivery status of every message you send. AfriRoute supports two complementary approaches: pull the current status with the status endpoint, or push status changes to your server via webhooks. Webhooks are recommended for production.

๐Ÿ”„ Delivery Statusesโ€‹

StatusMeaning
queuedAccepted by AfriRoute, awaiting dispatch
sentHanded off to the mobile operator
deliveredConfirmed delivered to the handset
failedCould not be delivered (see Error Codes)

๐Ÿ“ก Get Message Status (Pull)โ€‹

GET /v1/sms/{message_id}
ParameterInRequiredDescription
message_idpathYesThe ID returned when the message was sent

Example Requestโ€‹

curl https://api.afriroute.ai/api/v1/sms/msg_abc123xyz \
-H "Authorization: Bearer $AFRIROUTE_API_KEY"
const res = await fetch('https://api.afriroute.ai/api/v1/sms/msg_abc123xyz', {
headers: { 'Authorization': 'Bearer $AFRIROUTE_API_KEY' }
});
const status = await res.json();
console.log(status.status); // delivered
import requests

res = requests.get(
'https://api.afriroute.ai/api/v1/sms/msg_abc123xyz',
headers={'Authorization': 'Bearer $AFRIROUTE_API_KEY'},
)
print(res.json()['status'])

Responseโ€‹

{
"message_id": "msg_abc123xyz",
"status": "delivered",
"to": "+251911234567",
"from": "AFRIROUTE",
"parts": 1,
"cost": 0.05,
"currency": "ETB",
"created_at": "2026-05-28T10:30:00Z",
"delivered_at": "2026-05-28T10:30:05Z"
}

๐Ÿ”” Delivery Webhooks (Push)โ€‹

Provide a callback_url when sending a message (see Send SMS). AfriRoute will POST a JSON payload to that URL each time the message's status changes.

Webhook Payloadโ€‹

{
"event": "sms.status",
"message_id": "msg_abc123xyz",
"status": "delivered",
"to": "+251911234567",
"from": "AFRIROUTE",
"parts": 1,
"error_code": null,
"metadata": { "order_id": "ord_9981" },
"timestamp": "2026-05-28T10:30:05Z"
}

Handling the Webhookโ€‹

app.post('/webhooks/sms', async (req, res) => {
const { message_id, status, error_code } = req.body;

// Respond quickly with 200 so AfriRoute does not retry
res.sendStatus(200);

await updateMessageStatus(message_id, status, error_code);
});
from flask import Flask, request

app = Flask(__name__)

@app.post('/webhooks/sms')
def sms_webhook():
payload = request.get_json()
update_message_status(payload['message_id'], payload['status'])
return '', 200

Retries & Reliabilityโ€‹

  • Your endpoint must return a 2xx within 5 seconds, or the delivery is treated as failed.
  • Failed deliveries are retried with exponential backoff for up to 24 hours.
  • The same status may be delivered more than once โ€” make your handler idempotent using message_id.

Verifying Authenticityโ€‹

Each webhook includes an X-AfriRoute-Signature header containing an HMAC-SHA256 of the raw body signed with your webhook secret. Recompute and compare it before trusting the payload.

const crypto = require('crypto');

function verify(rawBody, signature, secret) {
const expected = crypto.createHmac('sha256', secret)
.update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}

๐Ÿ’ก Best Practicesโ€‹

  • Prefer webhooks over polling for accurate, low-latency status.
  • Respond 200 immediately, then process asynchronously.
  • Make handlers idempotent to tolerate duplicate deliveries.
  • Always verify the signature before acting on a payload.
  • Use metadata to correlate callbacks with your own records.

Last Updated: May 2026 ยท Need help? Contact Support โ†’