Event types
Lisoloo emits five event types, one per status transition. All share
the same outer envelope; the data block varies per event.
Envelope
Section titled “Envelope”{ "event_id": "f8a3b7c1-2e4d-4a9b-9d8f-7c6e5b4a3d2c", "event_type": "<type>", "timestamp": "2026-05-27T10:15:08Z", "message_id": "507f1f77bcf86cd799439011", "data": { ... }}| Field | Type | Notes |
|---|---|---|
event_id | UUID | Unique per event. Use for idempotency. |
event_type | string | One of sms.queued, sms.processing, sms.sent, sms.delivered, sms.failed. |
timestamp | ISO 8601 | When the gateway emitted the event. |
message_id | string | The message_id from POST /send. |
data | object | Event-specific payload. |
sms.queued
Section titled “sms.queued”Fired immediately after POST /send returns 201.
{ "event_id": "...", "event_type": "sms.queued", "timestamp": "2026-05-27T10:15:00Z", "message_id": "507f1f77bcf86cd799439011", "data": { "total_recipients": 3, "total_messages": 3, "total_cost": 0.06, "currency": "USD", "scheduled_at": null }}For scheduled sends, scheduled_at is the first (date, time) pair
from scheduled_dates (or the first computed occurrence for
recurring). Otherwise null — sms.processing will follow within
seconds.
sms.processing
Section titled “sms.processing”Fired when the carrier connector picks up the job.
{ "event_type": "sms.processing", "data": { "recipient_phone": "+243998857000", "carrier": "vodacom_drc" }}One event per recipient. carrier is the internal connector slug —
informational only, not part of the stable API.
sms.sent
Section titled “sms.sent”Fired when the SMSC accepts the message for delivery (carrier-side).
{ "event_type": "sms.sent", "data": { "recipient_phone": "+243998857000", "carrier": "vodacom_drc", "sent_at": "2026-05-27T10:15:03Z" }}The recipient has not yet received the SMS at this point — it’s in the carrier’s outbound queue.
sms.delivered
Section titled “sms.delivered”Fired when the handset ACKs delivery (or the carrier reports “delivered” in its asynchronous receipt).
{ "event_type": "sms.delivered", "data": { "recipient_phone": "+243998857000", "carrier": "vodacom_drc", "delivered_at": "2026-05-27T10:15:08Z", "segments": 1 }}Terminal for this recipient.
sms.failed
Section titled “sms.failed”Fired on any path to failed.
{ "event_type": "sms.failed", "data": { "recipient_phone": "+243998857000", "carrier": "vodacom_drc", "failed_at": "2026-05-27T10:15:05Z", "error_code": "1502", "error_message": "Number unreachable", "retryable": false }}Terminal. The error_code matches the error catalogue for
the carrier-level cause. The gateway does not retry failed SMS on
its own — the carrier has already attempted its built-in retries
before reporting failed.
Ordering
Section titled “Ordering”Events fire in the order the gateway observes the state machine
transitions, but the HTTP delivery order is not guaranteed. A
slow response from your endpoint on sms.processing followed by a
fast response on sms.delivered can result in delivered arriving
first.
If strict ordering matters, ignore event_type from the wire and
instead fetch GET /status/{message_id} on receipt of any event —
treat the webhook as a “something changed” signal.
Idempotency
Section titled “Idempotency”Your handler must be idempotent on event_id. The gateway will
retry on 5xx or timeout; the retry carries the same event_id.
A typical implementation:
def handle_event(event: dict, db) -> None: event_id = event["event_id"] if db.events.find_one({"event_id": event_id}): return # already processed process_event(event, db) db.events.insert_one({"event_id": event_id, "received_at": now()})See also
Section titled “See also”- Webhooks overview — delivery semantics
- Webhook configuration — Basic auth setup
- Message lifecycle — the state machine