Skip to content

Event types

Lisoloo emits five event types, one per status transition. All share the same outer envelope; the data block varies per event.

{
"event_id": "f8a3b7c1-2e4d-4a9b-9d8f-7c6e5b4a3d2c",
"event_type": "<type>",
"timestamp": "2026-05-27T10:15:08Z",
"message_id": "507f1f77bcf86cd799439011",
"data": { ... }
}
FieldTypeNotes
event_idUUIDUnique per event. Use for idempotency.
event_typestringOne of sms.queued, sms.processing, sms.sent, sms.delivered, sms.failed.
timestampISO 8601When the gateway emitted the event.
message_idstringThe message_id from POST /send.
dataobjectEvent-specific payload.

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 nullsms.processing will follow within seconds.

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.

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.

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.

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.

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.

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()})