Event envelope
On the 2026-06-01 webhook version, every event body is wrapped in the
same envelope. Event-specific fields live under data; everything else
is metadata about the delivery. This page is the reference for the
envelope. See Call events,
Stream events, and
Recording events for data schemas.
{ "id": "evt_01H8ZXK9M2P7Q3R4S5T6V7W8XY", "type": "call.answered", "api_version": "2026-06-01", "occurred_at": "2026-06-01T11:30:48.412Z", "account_id": "acc_01H8ZXK9M2P7Q3R4S5T6V7W8XY", "voice_app_id": "va_01H8ZXK9M2P7Q3R4S5T6V7W8XY", "sip_trunk_id": null, "call_id": "cs_01H8ZXK9M2P7Q3R4S5T6V7W8XY", "leg_id": "cl_01H8ZXK9M2P7Q3R4S5T6V7W8XY", "data": { "call_id": "cs_01H8ZXK9M2P7Q3R4S5T6V7W8XY", "from": "+91XXXXXXXXXX", "to": "+91XXXXXXXXXX", "direction": "inbound", "start_time": "2026-06-01T11:30:45.100Z", "answered_at": "2026-06-01T11:30:48.412Z" }, "previous_attributes": null}Fields
Section titled “Fields”| Field | Type | Nullable | Description |
|---|---|---|---|
id | string (evt_<ULID>) | no | Unique event ID. Stable across retries and replays. Dedupe on this. |
type | string | no | Event name. See the event catalog. |
api_version | string | no | Always "2026-06-01". |
occurred_at | ISO-8601 UTC | no | When the underlying event happened (not when this HTTP request was sent). Stable across retries. |
account_id | string (acc_<ULID>) | no | Your Teler account. |
voice_app_id | string (va_<ULID>) | yes | Owner for Voice App events. null on SIP Trunk events. |
sip_trunk_id | string (st_<ULID>) | yes | Owner for SIP Trunk events. null on Voice App events. |
call_id | string | yes | Always cs_<ULID>, for both Voice App and SIP-trunk calls. null on events that predate the call session (rare). |
leg_id | string (cl_<ULID>) | yes | Carried on both call events and per-leg events (leg.created, leg.answered, leg.completed), identifying the leg the event relates to. |
data | object | no | Event-specific payload. Schema depends on type. |
previous_attributes | null | yes | Reserved for future *.updated events. Always null today. |
Exactly one of voice_app_id or sip_trunk_id is set on every event:
never both, never neither.
data.call_id mirrors envelope-root call_id for compatibility with
handlers that only look inside data. The two fields always agree.
Idempotency contract
Section titled “Idempotency contract”id is stable across the entire lifecycle of an event:
- First delivery, HTTP retry #1, retry #2, …, retry #5: same
id. - Manual replay via the Dashboard or Replay API: same
id. - Recovery scanner republishing after an outage: same
id.
occurred_at is likewise stable: it’s the time of the underlying
event, not the delivery attempt.
How to dedupe: store id on receipt. If you’ve already seen it,
respond 200 OK and drop. See Retries & idempotency
for a full pattern.
Delivery source
Section titled “Delivery source”Every delivery carries an X-Teler-Source header telling you which
subsystem produced it:
| Value | Meaning |
|---|---|
live | Normal in-flight delivery from the ingestion pipeline. |
replay | You (or a support engineer) triggered a replay. |
recovery | Automatic re-delivery after Teler detected a stuck row (broker outage, dispatcher crash, etc.). |
All three sources send the same id and the same body bytes.
Signatures also match; they’re computed over the raw body once and
attached at delivery. There is no observable difference in the payload
between a live delivery and a recovery re-delivery.
Ordering
Section titled “Ordering”Best-effort per call_id. Two events on the same call almost always
arrive in the order they occurred, but network reordering, HTTP retry
timing, and recovery re-deliveries can break that. If order matters
(e.g. don’t process call.completed before call.answered), reorder
by occurred_at before acting.
Events on different call_ids have no ordering guarantee at all.
Owner routing
Section titled “Owner routing”Which URL receives a given event is determined by the event’s owner, not by the event type:
- Events with
voice_app_idset → the Voice App’swebhook_url. - Events with
sip_trunk_idset → the SIP Trunk’swebhook_url. - Recording events → the URL of whichever Voice App or SIP Trunk owns the parent call.
Each owner may pin its own webhook_api_version. See API versioning.
Legacy comparison at a glance
Section titled “Legacy comparison at a glance”{ "event": "call.answered", "account_id": "<uuid>", "call_app_id": "<uuid>", "data": { "call_id": "<fs-uuid>", "answer_time": "...", ... }}{ "id": "evt_...", "type": "call.answered", "api_version": "2026-06-01", "occurred_at": "...", "account_id": "acc_...", "voice_app_id": "va_...", "sip_trunk_id": null, "call_id": "cs_...", "leg_id": "cl_...", "data": { "call_id": "cs_...", "answered_at": "...", ... }, "previous_attributes": null}Full diff and migration steps: API versioning.