API reference
Webhooks & callbacks
Connect calls your endpoints over HTTPS. You set each URL in one of three places: on the number in the dashboard, in the body of POST /api/calls, or as an attribute in your markup. There are no subscriptions to create and no webhooks API. This page lists every request Connect sends. For a walkthrough of how they fit together, read Webhooks and callbacks.
Connect is live. The API is v0.x, so field names may change before v1.
Every request Connect sends
Voice webhooks, action URLs and wait URLs expect markup back (XML, or the smaller JSON form; see Call-control markup). Callbacks only need a 2xx; Connect ignores their response body.
| Request | Where it is set | Body | X-CPaaS-Event |
|---|---|---|---|
| Inbound voice webhook | Webhook URL on the number, in the dashboard | {from, to, direction: "inbound"}. Reply with markup. | webhook.inbound |
| Outbound voice webhook | url on POST /api/calls | {call_sid, to, from, direction: "outbound"}, sent once the callee answers. Reply with markup. | webhook.outbound |
| Gather action | action on <Gather> | {"digit": "1"}, or {"digit": ""} on timeout. Always a POST. Reply with markup. | gather.action |
| Dial action | action on <Dial> | DialCallStatus, DialCallDuration, plus RecordingUrl and the Recording fields when the leg was recorded, and ConferenceSid for a conference. Reply with markup. | dial.action, dial.queue.action, dial.conference.action |
| Record action | action on <Record> | RecordingName, RecordingUrl, RecordingDuration and the Recording fields. Reply with markup. | record.action |
| Wait URL | waitUrl on <Enqueue> or <Conference> | An empty JSON object. Reply with Say, Play or Wait; Connect loops them while the caller waits. | wait.url |
| Status callback | statusCallback on POST /api/calls, or the Hangup URL on the number | JSON with Twilio-style names (CallSid, CallStatus...). See Calls for every field. | call.status, call.hangup, status.callback.initiated |
| Recording status callback | recordingStatusCallback on <Dial>, <Record> or <Conference>, or the recording callback URL on the number | RecordingStatus, RecordingName, RecordingUrl... See Recordings for every field. | recording.status.<status> |
| Conference status callback | statusCallback on <Conference> | StatusCallbackEvent, ConferenceSid, CallSid... See Conferences for every field. | conference.<event> |
Bodies are JSON on a POST. When a method attribute is set to GET (Dial and Record action URLs, wait URLs, conference and recording callbacks), the same fields go in the query string instead.
Headers
Every request carries the same identifying headers, so you can route or log by header before you parse the body. X-CPaaS-Call-Sid is set whenever the request belongs to one call. Wait URL fetches and conference start and end events do not have it.
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| X-CPaaS-Event | Sent for |
|---|---|
| webhook.inbound | The inbound voice webhook on a number. |
| webhook.outbound | The url you passed to POST /api/calls, after the callee answers. |
| gather.action | A Gather action URL. |
| dial.action | A Dial action URL after a number or SIP leg. |
| dial.queue.action | A Dial action URL after <Dial><Queue>. |
| dial.conference.action | A Dial action URL after <Dial><Conference>. |
| record.action | A Record action URL. |
| wait.url | A waitUrl on Enqueue or Conference. |
| dial.screening | The url on a <Number> inside Dial, fetched before that leg is connected. |
| status.callback.initiated | The initiated status callback, sent only when you subscribe to it. |
| call.status | A status callback while the call is live (ringing, in-progress). |
| call.hangup | The final status callback for a call. |
| status.callback.<event> | A statusCallback set on a <Number> inside Dial. |
| recording.status.<status> | A recording status callback: in-progress, completed, absent or failed. |
| conference.<event> | A conference status callback: start, end, join or leave. |
Delivery rules
- 5 second timeout. This applies to the inbound webhook, action URLs, wait URLs and every callback. The outbound webhook has no fixed limit, but the person who answered hears silence until you reply.
- Sent once, no retries. If your endpoint is down, slow or returns an error, that request is lost. Use
GET /api/calls/{callSid}to catch up on a call. - Not signed. There is no signature header. See below for how to protect your endpoint.
- If the inbound webhook fails, the caller hears a short message and the call ends. If an action URL fails, the call has nothing left to run and ends.
- Ordering. Status callbacks carry
SequenceNumberandCallbackId(CallSid plus SequenceNumber).IsFinalis true on the last one.
Securing your endpoint
Because requests are not signed, anyone who learns one of your URLs can post to it. Three checks keep that safe:
- Put a long random token in each URL you give Connect (in the path or the query string), serve it over HTTPS, and reject any request without it.
- Check
X-CPaaS-Call-Sid(orCallSidin the body) against the calls you placed or received. - Before you act on something that matters, such as billing or fulfilling an order, confirm the call with
GET /api/calls/{callSid}using your API key.
// Express. The token is part of the URL you gave Connect, for example
// statusCallback: "https://your.app/voice/status/" + process.env.CALLBACK_TOKEN
app.post("/voice/status/:token", express.json(), async (req, res) => {
if (req.params.token !== process.env.CALLBACK_TOKEN) return res.sendStatus(404);
const callSid = req.get("X-CPaaS-Call-Sid");
if (!callSid || !(await isOurCall(callSid))) return res.sendStatus(404);
res.sendStatus(204); // answer fast: there are no retries
jobs.push(req.body); // do the slow work after replying
});