Concept

Webhooks & callbacks

TelVox reaches your app in three ways. The webhook asks what to do with a live call, and its action URLs ask again after Gather, Dial and Record. Status callbacks report each step of a call. Recording status callbacks report each step of a recording. All of them are HTTP requests with JSON bodies.

API stability

Connect is live. Callback field names follow Twilio's where one exists. The API is v0.x, so field names, especially the extra TelVox ones, may change before v1.

Webhook vs status callback

These look alike but do different jobs. The webhook is synchronous and instructive: TelVox waits for your reply and runs the call-control markup you return. A status callback is informational: TelVox tells you the call changed state and only needs a quick 2xx. The call carries on whatever you return.

Webhook and action URLsStatus callbacks
Where setInbound: the number's webhook URL in the dashboard. Outbound: url on POST /api/calls. Action URLs: the action attribute in your markup.Outbound: statusCallback on POST /api/calls. Inbound: the number's status callback URL in the dashboard.
Your replyXML or JSON markup.Any 2xx. The body is ignored.
EffectDrives the live call.Reports state. Doesn't steer.

Inbound webhooks and action URLs time out after 5 seconds. If the inbound webhook fails, the caller hears a short message and the call ends. The webhook bodies and action URL bodies are listed on the call-control markup page.

Status callbacks

Pass statusCallback when you create a call (for inbound calls, set it on the number in the dashboard). TelVox POSTs JSON there as the call moves on. You always get ringing, in-progress and one final status. initiated comes only if statusCallbackEvent includes it.

CallStatusMeaning
initiatedThe call is being placed. Sent only if statusCallbackEvent includes initiated.
ringingThe destination is ringing.
in-progressThe call was answered.
completedFinal. The call was answered and has ended.
busyFinal. The destination was busy.
no-answerFinal. Nobody answered.
failedFinal. The call could not be connected.
canceledFinal. The call was ended before it was answered.

The body uses Twilio's CallProgressEvents field names (CallSid, CallStatus, From, To, Direction, SequenceNumber, Timestamp) plus a few TelVox fields such as WebhookEvent, CallbackId and IsFinal. The final callback has WebhookEvent set to call.hangup, IsFinal set to true, and adds Duration (whole minutes, rounded up) and CallDuration (seconds), both as strings, with HangupSource, HangupReason and HangupCause when known. Fields with no value are left out.

POSTyour statusCallback
POST /voice/status HTTP/1.1
Host: your.app
Content-Type: application/json
User-Agent: cPaaS-Platform/1.0
X-CPaaS-Event: call.status
X-CPaaS-Call-Sid: CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5

{
  "ApiVersion": "2010-04-01",
  "AccountSid": "AC3b1f...",
  "CallSid": "CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5",
  "CallStatus": "in-progress",
  "Direction": "outbound-api",
  "From": "+918045670100",
  "To": "+919876500199",
  "Caller": "+918045670100",
  "Called": "+919876500199",
  "CallerCountry": "IN",
  "FromCountry": "IN",
  "CallbackSource": "call-progress-events",
  "SequenceNumber": "2",
  "Timestamp": "Mon, 05 Oct 2026 10:15:04 +0000",
  "EventTimestamp": "2026-10-05T10:15:04.000Z",
  "WebhookEvent": "call.status",
  "EventType": "call.status",
  "CallbackId": "CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5:2",
  "IsFinal": false,
  "WebhookUrl": "https://your.app/voice/answer"
}
your app → TelVox
HTTP/1.1 200 OK

# Any 2xx, within 5 seconds. The body is ignored.
# Do slow work (database writes, other APIs) on a queue.

Recording status callbacks

A separate callback reports each step of a recording, so you know when a GET on the RecordingUrl (/api/recordings/{name}, with your API key) will return the file. Set it with the recordingStatusCallback attribute on <Dial>, <Record> or <Conference> in your markup. By default only completed is sent. List more in recordingStatusCallbackEvent, and use recordingStatusCallbackMethod to switch to GET.

RecordingStatusMeaning
in-progressRecording has started.
completedRecording finished. The WAV file is stored and can be downloaded.
absentNo recording was produced, for example because nothing was captured.
failedRecording could not be completed.
POSTyour recordingStatusCallback
POST /voice/rec-status HTTP/1.1
Host: your.app
Content-Type: application/json
User-Agent: cPaaS-Platform/1.0
X-CPaaS-Event: recording.status.completed
X-CPaaS-Call-Sid: CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5

{
  "RecordingStatus": "completed",
  "RecordingName": "cpaas-channel-CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5-1759659351000",
  "RecordingUrl": "https://connect.telvox.dev/api/recordings/cpaas-channel-CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5-1759659351000",
  "RecordingDuration": 47,
  "RecordingCompletedAt": "2026-10-05T10:15:51.000Z",
  "CallSid": "CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5"
}

Securing your endpoint

Requests from TelVox are not signed. To be sure a request came from TelVox and belongs to you:

  • Use HTTPS, and put a long random token in the callback URL (in the path or the query string). Check it on every request and reject anything without it.
  • Check X-CPaaS-Call-Sid against the calls you placed, or the calls to numbers you own.
  • Before acting on anything important, confirm it with GET /api/calls/{callSid}.

Every request carries User-Agent: cPaaS-Platform/1.0, X-CPaaS-Event (for example call.status, call.hangup or recording.status.completed) and X-CPaaS-Call-Sid. These help you route and log requests, but anyone can set them, so they are not proof on their own.

Delivery

Each callback is sent once, with a 5 second timeout. It is not retried. If your endpoint is down or slow, that callback is lost.

  • Reply 2xx fast and do slow work on a queue, so the handler never times out.
  • De-duplicate on CallbackId (CallSid:SequenceNumber).
  • IsFinal marks the last callback for a call.
  • Order events by SequenceNumber. It is sent as a string, so compare it as a number.
  • To catch up on anything you missed, fetch the call with GET /api/calls/{callSid}.
status callback handler
import express from "express";

const app = express();
app.use(express.json());

// The statusCallback you pass is
// https://your.app/voice/status/<long random token>
const TOKEN = process.env.CALLBACK_TOKEN;
const seen = new Set(); // use your database in production

app.post("/voice/status/:token", (req, res) => {
  if (req.params.token !== TOKEN) return res.sendStatus(404);

  const sid = req.get("X-CPaaS-Call-Sid");
  if (sid !== req.body.CallSid) return res.sendStatus(400);

  res.sendStatus(200); // reply first, inside 5 seconds

  if (seen.has(req.body.CallbackId)) return; // already handled
  seen.add(req.body.CallbackId);

  // Check sid against calls you placed, then queue the work.
  // Order events by Number(req.body.SequenceNumber).
  // req.body.IsFinal === true marks the last callback for the call.
  queue.push(req.body);
});
GET/api/calls/CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5
curl https://connect.telvox.dev/api/calls/CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5 \
  -H "Authorization: Bearer $TELVOX_API_KEY" \
  -H "Accept: application/json"
200 ok

Next: Webhooks & callbacks reference (every request Connect sends) · Calls reference