Skip to content

Concept

Webhooks & status callbacks

TelVox reaches your application in three ways: a synchronous answer-URL request that asks what to do with a live call, status callbacksthat report one call's progress, and event webhooks that fire on lifecycle events across your account. All of it travels over the same signed, SSRF-safe egress that powers Dial's integrations.

Developer preview

Payload field names and the signature header format below are illustrative and may change before general availability. The delivery engine itself is real and shipped: the nine lifecycle events, custom headers, JSON payload templates, retries with timeouts, encrypted secrets, and the per-organization host allow-list are all in production in Dial today.

answer_url vs status_callback

These look similar but do opposite jobs. The answer_url is synchronous and instructive: TelVox blocks on your response and runs the call-control document you return. A status_callback is asynchronous and informational: TelVox tells you a call changed state and expects only a quick acknowledgement — the call proceeds regardless of what you return.

answer_urlstatus_callback
TimingSynchronous — TelVox waits.Asynchronous — fire-and-forget.
Your replyA call-control document.A 2xx acknowledgement.
EffectDrives the live call.Records state; doesn't steer.

Status callbacks

Attach a status_callback URL when you create a call to follow its lifecycle. TelVox POSTs as the call moves through initiated, ringing, answered and completed. Recording produces its own callback stream — in-progress/completed/absent/failed — covered below.

POSTyour status_callback
POST /voice/status HTTP/1.1
Host: your.app
Content-Type: application/json
TelVox-Signature: t=1718995200,v1=5d41402abc4b2a76b9719d911017c592...

{
  "call_sid": "CA9f3a2b...",
  "event": "answered",
  "from": "+14155550100",
  "to": "+14155550199",
  "direction": "outbound",
  "timestamp": "2026-06-22T18:01:14Z"
}
your app → TelVox
// Acknowledge quickly. A 2xx means "received" — do slow work
// (DB writes, downstream calls) on a queue, not inline.
HTTP/1.1 200 OK

Recording-status callbacks

A separate callback reports the state of a recording, so you know when a signed-URL fetch will succeed.

RecordingStatusMeaning
in-progressRecording has started and is actively capturing.
completedRecording finished and is available for retrieval.
absentNo recording was produced (e.g. nothing was captured).
failedRecording could not be completed.

Event webhooks

Where status callbacks track a single call you started, event webhooks notify you of lifecycle events across your account — independent of any one request. You subscribe an endpoint to the event types you care about; each subscription can carry custom headers and a JSON payload template. TelVox supports nine lifecycle events today.

EventFires when
web-formA web form or inbound lead submission was captured.
call-startA call connected and began.
hangupA call ended; carries the final disposition timing.
no-agentNo agent was available to take a routed call.
dispositionA call was wrapped with an outcome/disposition code.
callbackA scheduled callback came due or was requested.
queuedA call entered an ACD queue to wait for an agent.
abandonedA waiting caller hung up before being answered.
voicemail-leftA caller left a voicemail; links the recording.
POSTyour webhook endpoint
POST /telvox/events HTTP/1.1
Host: your.app
Content-Type: application/json
TelVox-Signature: t=1718995200,v1=a3f1...
X-My-Custom-Header: configured-per-subscription

{
  "event": "voicemail-left",
  "id": "EVb71c...",
  "occurred_at": "2026-06-22T18:09:40Z",
  "data": {
    "call_sid": "CA9f3a2b...",
    "recording_sid": "RE5e22...",
    "duration": 23
  }
}

Signed & SSRF-safe delivery

Every delivery is signed and every target is checked against a per-organization host allow-list before TelVox connects to it.

  • Signatures. Each request carries a TelVox-Signature header — an HMAC over the timestamp and raw body, keyed by your subscription secret (stored encrypted at rest). Verify it before trusting a payload.
  • SSRF-safe egress.Outbound delivery is restricted to an allow-list of hosts you configure; requests to internal, private or link-local addresses are blocked, so a misconfigured URL can't be turned into a server-side request forgery vector.
  • Replay protection. The signed timestamp lets you reject stale deliveries — drop anything outside a short window (a few minutes) even if the signature is otherwise valid.

Verifying a signature

Recompute the HMAC over the timestamp and the raw request body (not a re-serialized object — re-ordering keys breaks the hash), then compare in constant time. Reject deliveries whose timestamp is too old.

verify webhook signature
import crypto from "node:crypto";

// Recompute the signature over the raw body + timestamp and
// compare in constant time. Reject anything older than ~5 min.
export function verify(rawBody, header, secret) {
  const { t, v1 } = parseHeader(header); // "t=...,v1=..."
  if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(v1),
  );
}

Retries, timeouts & idempotency

TelVox treats a fast 2xxas success. If your endpoint times out or returns a non-2xx, delivery is retried with backoff up to a per-subscription limit; deliveries are rate-limited so a burst of events can't overrun your endpoint. Acknowledge first and do the slow work on a queue — keep the handler fast so it never times out.

  • Make handlers idempotent — key off the event id so a retried delivery is processed once.
  • Return 2xx as soon as you've durably received the event; defer downstream work to a background job.
  • Events can arrive out of order or more than once under retry — design for at-least-once delivery.

Next: Webhooks reference · Calls reference