Versioning
Teler pins the webhook payload shape and event catalog to a dated version per Voice App and per SIP Trunk. Existing pins never change on their own; you decide when to migrate.
Two versions are available today.
| Version | Status | Wire format | Additional events |
|---|---|---|---|
2025-08-01 | Frozen. Continues to work for existing pins. | Flat body, direct HTTP delivery. | none |
2026-06-01 | Available for new pins. | Envelope with id, type, api_version, unified field vocabulary. | call.ringing |
Only the webhook wire format and event catalog are versioned. Call Flow JSON, SDK method signatures, and the media-streaming WebSocket protocol are unaffected by this pin.
Pinning a version
The pin lives on the owning resource as webhook_api_version. Set it when you create the Voice App or SIP Trunk in the dashboard; change it later by editing the same resource.
Valid values: "2025-08-01" or "2026-06-01". Any other value is rejected.
Changing the pin takes effect on the next event for that resource. Deploy your 2026-06-01-capable handler before flipping the pin.
Identifying the version at runtime
Two ways. Use whichever fits your handler.
HTTP header (present on every 2026-06-01 request, absent on 2025-08-01):
X-Teler-Api-Version: 2026-06-01
Body field at the envelope root (2026-06-01 only):
{ "api_version": "2026-06-01", "type": "call.initiated", "...": "..." }
If neither is present, the event is 2025-08-01.
Body shape
2025-08-01 sends a flat body with the event name inside:
{
"event": "call.initiated",
"account_id": "<uuid>",
"call_app_id": "<uuid>",
"data": { "call_id": "<uuid>", "from": "...", "to": "...", "...": "..." }
}
2026-06-01 wraps the payload in an envelope:
{
"id": "evt_01H8ZXK9M2P7Q3R4S5T6V7W8XY",
"type": "call.initiated",
"api_version": "2026-06-01",
"occurred_at": "2026-06-01T11:30:45.100Z",
"account_id": "acc_01H8ZXK9M2P7Q3R4S5T6V7W8XY",
"voice_app_id": "va_01H8ZXK9M2P7Q3R4S5T6V7W8XY",
"sip_trunk_id": null,
"call_id": "cs_01H8ZXK9M2P7Q3R4S5T6V7W8XY",
"leg_id": null,
"data": { "call_id": "cs_...", "...": "..." },
"previous_attributes": null
}
Exactly one of voice_app_id or sip_trunk_id is set on every event, never both.
Envelope fields
| Field | Type | Nullable | Description |
|---|---|---|---|
id | string (evt_<ULID>) | no | Unique event ID, stable across retries and replays. Use as your idempotency key. |
type | string | no | Event name. See per-event pages under Webhooks. |
api_version | string | no | Always "2026-06-01". |
occurred_at | ISO-8601 UTC | no | When the underlying event happened. Stable across retries. |
account_id | string (acc_<ULID>) | no | Your Teler account. |
voice_app_id | string (va_<ULID>) | yes | Set on Voice App events; null on SIP Trunk events. |
sip_trunk_id | integer | yes | Set on SIP Trunk events; null on Voice App events. |
call_id | string (cs_<ULID>) | yes | Identifies the call. Same format for Voice App and SIP Trunk calls. |
leg_id | string (cl_<ULID>) | yes | Reserved for future per-leg events. null on today's event catalog. |
data | object | no | Event-specific payload. |
previous_attributes | null | yes | Reserved for future *.updated events. |
Field renames inside data
2025-08-01 | 2026-06-01 | Notes |
|---|---|---|
hangup_time | ended_at | Unified across call, leg, and SIP Trunk events. |
hangup_source | ended_by | Values also normalized (see below). |
duration (int, seconds) | duration_seconds (int, seconds) | Same units, explicit name. |
answer_time | answered_at | Aligned with ended_at. |
| (not present) | reason | New field. Normalized terminate reason such as "no_answer", "user_busy", "canceled". |
ended_by value mapping
2025-08-01 (hangup_source) | 2026-06-01 (ended_by) |
|---|---|
caller | caller |
callee | callee |
platform / system | system |
Identifiers
2025-08-01 uses raw UUIDs. 2026-06-01 uses prefixed, resource-typed IDs everywhere.
| Prefix | Refers to |
|---|---|
acc_ | Account |
va_ | Voice App |
st_ | SIP Trunk |
cs_ | Call Session (Voice App or SIP Trunk call) |
cl_ | Call Leg |
evt_ | Webhook Event |
rec_ | Recording |
pb_ | Playback |
data.call_id and envelope-root call_id always agree on 2026-06-01.
Headers
| Header | 2025-08-01 | 2026-06-01 |
|---|---|---|
X-Teler-Timestamp | yes | yes |
X-Teler-Signature | yes | yes |
X-Teler-Event-Id | no | yes (echoes envelope id) |
X-Teler-Api-Version | no | yes |
X-Teler-Source | no | yes (live | replay | recovery) |
Signature scheme is identical on both versions: HMAC-SHA256(secret, "{timestamp}.{raw_body}").
Event catalog
Events emitted on 2025-08-01:
call.initiated, call.answered, call.completed, call.failed, stream.initiated, stream.completed, recording.completed, recording.failed.
Events emitted on 2026-06-01. Everything above, plus:
| Event | What it means |
|---|---|
call.ringing | Callee's phone is ringing (outbound) or Teler is routing the call (inbound). |
Idempotency
2025-08-01 has no idempotency key. Duplicates are distinguishable only by comparing body content.
2026-06-01 guarantees a stable envelope id across every retry, replay, and recovery re-delivery. Store id on receipt and reject duplicates.
What is not different
| Aspect | Behavior on both versions |
|---|---|
| Signing algorithm | HMAC-SHA256 over "{timestamp}.{raw_body}" |
| Retry policy | Up to 8 attempts, exponential backoff |
| Timeout | 5 seconds for your response |
| Ordering | Best-effort per call_id. Reorder by timestamp if order matters. |
| Which secret is used | The one on the owning Voice App or SIP Trunk |
Migration checklist
Order matters. The goal is that your handler serves 2026-06-01 traffic correctly before you flip the pin.
- Deploy a handler that branches on
X-Teler-Api-Version(or on presence of theapi_versionbody field). Keep the2025-08-01parser online. - Point a low-traffic Voice App or SIP Trunk in staging at
2026-06-01. Confirm one call end-to-end. - Flip production resources one at a time. Watch for the terminal event (
call.completedorcall.failed) at the expected rate on the new pin. - Once nothing is emitting
2025-08-01, remove the2025-08-01parser.
The pin is per-resource. You can migrate one Voice App at a time and leave the rest on 2025-08-01.
Deprecation policy
2025-08-01is frozen but continues to work indefinitely for existing pins. No end-of-life date is set.- New dated versions ship when a wire-format change is not backward-compatible.