TelVox · Connect · Quickstart

Make your first call

This quickstart places a real outbound call and follows it from start to finish: create an API key, claim a number, POST to /api/calls with a url, reply with XML from that url, and handle the status callback as the call moves through ringing, in-progress and completed. Samples are shown in cURL, Node and Python.

API stability

Connect is live. We set up your account with you. After that you claim numbers and create API keys yourself in the dashboard. The API is v0.x, so field names may change before v1.

Before you start

You'll need:

  • An account: request access on the signup page and we'll set it up with you.
  • A public HTTPS URL for your url and statusCallback (a tunnel to your laptop works while testing).
  • Any HTTP client. There are no TelVox SDKs yet, so the samples use plain cURL, Node fetch and Python requests.

1 · Create an API key

An API key authenticates every request. Create one in the dashboard under API keys. It starts with sk_live_ and is shown once, so copy it then. TelVox keeps only a one-way hash, so nobody can show it to you again. Keep it on your server and send it as a Bearer token in the Authorization header. See Authentication & API keys for the full model.

~/.zshrc · export your key
export TELVOX_API_KEY="sk_live_…"   # created in the dashboard, shown once

2 · Claim a number

Calls can only be placed from a number you have claimed. In the dashboard, open Numbers and choose Claim a number. The number you claim is the from in the next step. If from is not one of your active numbers, the API answers 422 and the call is not placed. There is no API to search for or buy numbers. See Phone numbers for the settings on each number, including the webhook URL for inbound calls.

3 · Place a call

POST to /api/calls with from, to and url. Add statusCallback to follow the call, and list initiated in statusCallbackEvent if you want that event too. Send Accept: application/json so any error comes back as JSON. Your url has to be reachable before you run this; step 4 shows what it returns.

POST/api/calls
curl -X POST https://connect.telvox.dev/api/calls \
  -H "Authorization: Bearer $TELVOX_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{
    "from": "+918045670100",
    "to": "+919876500199",
    "url": "https://your.app/voice/answer",
    "statusCallback": "https://your.app/voice/status",
    "statusCallbackEvent": ["initiated"]
  }'
request

TelVox responds with 201 and the new call. The call_sid (CA plus 32 characters) identifies the call in the API and in every callback. The numbers above are placeholders; use your claimed number and a real destination.

/api/calls
{
  "call_sid": "CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5",
  "to": "+919876500199",
  "from": "+918045670100",
  "status": "initiated",
  "channel_id": "1719912345.1234"
}
201 createdinitiated

4 · Reply with XML from your url

When the callee answers, TelVox POSTs JSON call details to your url. Your app replies with TwiML-style XML, and TelVox runs the verbs in order: here <Say> greets the callee and <Gather> waits for one keypress.

POSTyour.app/voice/answer
POST /voice/answer HTTP/1.1
Content-Type: application/json
User-Agent: cPaaS-Platform/1.0
X-CPaaS-Event: webhook.outbound
X-CPaaS-Call-Sid: CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5

{
  "call_sid": "CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5",
  "to": "+919876500199",
  "from": "+918045670100",
  "direction": "outbound"
}
what your url receives
your.app/voice/answer · response
<Response>
  <Say>Thanks for calling.</Say>
  <Gather action="https://your.app/voice/menu" numDigits="1" timeout="5">
    <Say>Press 1 for sales, 2 for support.</Say>
  </Gather>
</Response>
200 · application/xml

When the caller presses a key, TelVox POSTs {"digit":"1"} to the Gather action URL (an empty digit on timeout), and your app returns the next XML. There is also a smaller JSON form, {"response":[{"say":"Hello"},{"hangup":true}]}, which covers say, play, dial, wait, hold, enqueue and hangup but not gather or record. See Call-control markup for every verb.

5 · Handle status callbacks

As the call advances, TelVox POSTs JSON to your statusCallback: ringing, in-progress, then a final status (completed, busy, no-answer, failed or canceled). initiated comes only if you listed it in statusCallbackEvent. Field names follow Twilio's. The last callback has IsFinal set to true and adds CallDuration in seconds.

Each callback is sent once, with a 5 second timeout, and is not retried. Reply with a 2xx quickly and do slow work later. Callbacks are not signed, so put a long random token in your callback URL and check it, and match X-CPaaS-Call-Sid against calls you placed.

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

{
  "ApiVersion": "2010-04-01",
  "CallSid": "CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5",
  "CallStatus": "in-progress",
  "Direction": "outbound-api",
  "From": "+918045670100",
  "To": "+919876500199",
  "Caller": "+918045670100",
  "Called": "+919876500199",
  "CallbackSource": "call-progress-events",
  "SequenceNumber": "2",
  "Timestamp": "Mon, 22 Jun 2026 17:04:19 +0000",
  "EventTimestamp": "2026-06-22T17:04:19.000Z",
  "WebhookEvent": "call.status",
  "EventType": "call.status",
  "CallbackId": "CA7Q2M9X4K1B8N3T6R0W5Y2Z9C1V4H7JL5:2",
  "IsFinal": false
}
sent once · 5 s timeoutin-progress

See Webhooks & callbacks for every field in the callback. If you miss one, fetch the call with GET /api/calls/{callSid}.

Record the call

To record, add the record attribute to <Dial> (for a bridged call), or use the <Record> verb (for example for voicemail). When the file is ready, TelVox POSTs a recording status callback with a RecordingUrl. Fetch the WAV from GET /api/recordings/{name} with your API key.

record a bridged call
<Response>
  <Say>This call is recorded.</Say>
  <Dial record="record-from-answer"
        recordingStatusCallback="https://your.app/voice/rec-status">
    <Number>+919876500111</Number>
  </Dial>
</Response>

What's next