You want your own systems — a CRM, a billing job, a compliance archive — to react the moment a call changes state: a new call came in, it was answered, it transferred to a human, it hung up with a given cause. That is what voice events are for. The engine emits one event for every lifecycle transition of a call, keyed by call_sid, and you consume them either as signed HTTP POSTs to an endpoint you register or off the real-time event bus your app already subscribes to.
The events described here are shipped and flow on every call today (they are the same records that power reports and dashboards). Programmable per-endpoint HTTP webhooks for voice ride the same signed-delivery system that powers TeleQuick Streams — signing, retry, and the delivery log below are that system. Voice event-type registration is rolling out on that shared delivery plane; confirm which call.* types your workspace can subscribe to in the console before you depend on push delivery, and use the real-time bus or the observability pipeline in the meantime.

What you can subscribe to

Every event is named call.<state>. One call produces a stream of them from initiated through cleared.
EventFires whenCarries
call.initiatedAn inbound INVITE arrives or you originate a callani, dnis, direction, trunk_id
call.deliveredThe call reached the destination (alerting / early media)
call.establishedThe call was answered (two-way media up)handler_id when AI/agent-handled
call.held / call.retrievedThe call was put on hold / taken off hold
call.reinvitedMedia was renegotiated mid-call (re-INVITE)
call.transferredA REFER transfer occurred
call.dtmfA DTMF digit was detected on the legdigit in cause
call.failedSetup failed before answercause (Q.850)
call.clearedThe call endedcause (Q.850), duration_sec
A segment carrying a handler_id was handled by an AI agent or a human agent — that is how downstream reporting splits a call into its AI leg and its human leg. Use it to attribute events to the agent that owned that stretch of the call.
Subscribe to a set of exact names or use * to receive everything. A prefix glob such as call.* matches every call event.

Event payload

Every event is a flat JSON object. Only fields the engine actually knows for that transition are present — absent fields are omitted, not null.
call_sid
string
required
The universal call identifier. Correlate every event, the CDR, recordings, and the audio bridge through this one key.
event
string
required
The lifecycle state, e.g. established or cleared (the call. prefix is on the event name, not repeated inside the payload).
tenant_id
string
required
The workspace the call belongs to.
timestamp_ms
integer
required
Event time in milliseconds since the Unix epoch. Use it for ordering and for rejecting stale re-deliveries.
cause
integer
Q.850 clearing cause on cleared/failed (e.g. 16 = normal clearing). On call.dtmf this carries the detected digit.
duration_sec
integer
Billable talk duration in seconds — present on cleared.
ani
string
Calling number (the “from”).
dnis
string
Dialed number (the “to”).
direction
string
inbound or outbound.
trunk_id
string
The SIP trunk the call matched or used.
handler_id
string
The agent that owns this segment, when the engine knows it (AI or outbound).
{
  "call_sid": "cs_9f3ab1c27d",
  "event": "established",
  "tenant_id": "org_123",
  "timestamp_ms": 1751990400123,
  "ani": "+14155550101",
  "dnis": "+18885550199",
  "direction": "inbound",
  "trunk_id": "trunk_pstn_a",
  "handler_id": "agent_sales_ai"
}
{
  "call_sid": "cs_9f3ab1c27d",
  "event": "cleared",
  "tenant_id": "org_123",
  "timestamp_ms": 1751990461984,
  "cause": 16,
  "duration_sec": 58,
  "ani": "+14155550101",
  "dnis": "+18885550199",
  "direction": "inbound"
}
When delivered over HTTP, this object is the raw POST body. When delivered over the real-time bus, the same object arrives as the data field of a call.<state> message on a channel scoped to the workspace.

Register an endpoint

Endpoints are configured in the console, on the same Webhooks screen used across products. Each endpoint gets its own HMAC signing secret.
1

Add an endpoint

Open Webhooks in the voice console (agent.telequick.dev), click Add endpoint, and enter the HTTPS URL plus the event types to receive — a comma-separated list where * matches everything (e.g. call.*).
2

Store the signing secret

On create, the console shows the per-endpoint signing secret once — it looks like whsec_…. Copy it and set it on your receiver; it never appears again.
The secret is displayed only at creation and cannot be retrieved later. If you lose it, delete the endpoint and add it again to mint a new one.
3

Pause, resume, or delete

Each row shows a health state — Healthy, Paused, or Failing (Nx) after N consecutive non-2xx responses. Pause to stop deliveries without losing the endpoint; delete to remove it (its delivery history stays in the logs).

Verify the signature

Every delivery is signed. The engine’s delivery worker computes HMAC-SHA256(secret, rawBody) and sends it as a hex digest in a signature header. Recompute it over the exact bytes you received (before any JSON parse) and compare in constant time. Reject anything that does not match, and reject payloads whose timestamp_ms is far outside your clock tolerance to blunt replays.
import { createHmac, timingSafeEqual } from "node:crypto";

// rawBody: the exact request body bytes; header: the signature header value.
export function verify(rawBody: Buffer, header: string, secret: string): boolean {
  const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(header);
  return a.length === b.length && timingSafeEqual(a, b);
}

// Express example — capture the RAW body, do NOT let a JSON parser touch it first.
app.post("/hooks/voice", express.raw({ type: "application/json" }), (req, res) => {
  const sig = String(req.header("x-webhook-signature") ?? "");
  if (!verify(req.body, sig, process.env.VOICE_WEBHOOK_SECRET!)) {
    return res.status(401).end();          // bad signature → reject
  }
  const event = JSON.parse(req.body.toString("utf8"));
  // ... react to event.event / event.call_sid, then:
  res.status(200).end();                   // 2xx ACKs; anything else retries
});
Verify against the raw request bytes. If a framework parses and re-serializes the JSON before you hash it, key ordering and whitespace change and every signature will fail. Capture the body raw on this route.

Delivery, retry, and inspection

  • Acknowledge with 2xx. Any 2xx response marks the delivery successful. Any other status (or a timeout / connection error) is treated as a failure and retried with backoff.
  • Retries. Failed deliveries are re-fired automatically by the delivery worker with increasing spacing. After a run of consecutive failures the endpoint flips to Failing (Nx); sustained failure pauses it so a dead receiver cannot back up the queue.
  • Delivery log. The console keeps a per-delivery record — event type, target object, endpoint, response code, a short response-body preview, attempt number, and timestamp — filterable by All / Delivered / Failed.
  • Resend. Any delivery row can be re-queued from the log with Resend, which re-fires the same event within seconds. Use it after fixing a receiver that was returning errors.
Make your handler idempotent. Retries and manual resends mean the same (call_sid, event, timestamp_ms) can arrive more than once; dedupe on that tuple rather than assuming exactly-once delivery.

Consume events without an endpoint

You do not have to run a public HTTPS receiver. The same events are available in two other places today:

Real-time event bus

call.<state> messages are published live on a workspace-scoped channel — the same bus the agent control channel and consoles subscribe to. Best for driving a UI in real time.

Observability pipeline

Every lifecycle transition is folded per call_sid into the durable reporting store behind dashboards and CDRs. Best for after-the-fact reporting and reconciliation.

Calls API

Originate, look up, transfer, and hang up — the control plane for the call_sid these events key off.

Call traces

Follow one call end-to-end across signalling, media, and the agent runtime.

Agent control channel

The event surface for human-agent softphones: offers, state changes, accept and reject.

Errors

Status codes and failure causes you will see on call.failed and call.cleared.