Skip to content

Event envelope

Requires webhook version 2026-06-01

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
}
FieldTypeNullableDescription
idstring (evt_<ULID>)noUnique event ID. Stable across retries and replays. Dedupe on this.
typestringnoEvent name. See the event catalog.
api_versionstringnoAlways "2026-06-01".
occurred_atISO-8601 UTCnoWhen the underlying event happened (not when this HTTP request was sent). Stable across retries.
account_idstring (acc_<ULID>)noYour Teler account.
voice_app_idstring (va_<ULID>)yesOwner for Voice App events. null on SIP Trunk events.
sip_trunk_idstring (st_<ULID>)yesOwner for SIP Trunk events. null on Voice App events.
call_idstringyesAlways cs_<ULID>, for both Voice App and SIP-trunk calls. null on events that predate the call session (rare).
leg_idstring (cl_<ULID>)yesCarried on both call events and per-leg events (leg.created, leg.answered, leg.completed), identifying the leg the event relates to.
dataobjectnoEvent-specific payload. Schema depends on type.
previous_attributesnullyesReserved 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.

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.

Every delivery carries an X-Teler-Source header telling you which subsystem produced it:

ValueMeaning
liveNormal in-flight delivery from the ingestion pipeline.
replayYou (or a support engineer) triggered a replay.
recoveryAutomatic 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.

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.

Which URL receives a given event is determined by the event’s owner, not by the event type:

  • Events with voice_app_id set → the Voice App’s webhook_url.
  • Events with sip_trunk_id set → the SIP Trunk’s webhook_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.

{
"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.