Guides
Telephony & SIP
Put your workflows on real phone numbers. Vollo speaks SIP to any carrier — Twilio Elastic SIP Trunking is the primary, best-tested path — and routes inbound calls to workflows with dispatch rules.
How the pieces fit#
Three resources, in order:
- A SIP trunk is the carrier connection — where inbound INVITEs come from and where outbound calls are sent.
- A phone number is an E.164 number that lives on a trunk. You buy and manage the number at your carrier; Vollo just needs to know it exists and which trunk carries it.
- Dispatch rules on a number decide which workflow answers an inbound call — optionally filtered by time windows and caller ID, evaluated in priority order.
All three are managed in the dashboard under Telephony, or via the API with the telephony scope.
SIP trunks#
Twilio Elastic SIP Trunking (recommended)#
You will connect the two platforms in both directions: origination (Twilio delivers inbound calls to Vollo) and termination (Vollo sends outbound calls to Twilio). Worked example for a workspace whose SIP domain is acme:
-
Create the trunk in Vollo
cURL / shellCreate a SIP trunk curl -X POST https://api.vollo.io/api/v1/sip-trunks \ -H "Authorization: Bearer pk_live_8f3KJd92mA4qL7Zx1RttVWpc" \ -H "Content-Type: application/json" \ -d '{ "name": "Twilio production", "provider": "twilio", "termination_uri": "vollo-acme.pstn.twilio.com", "auth_username": "vollo-prod", "auth_password": "REPLACE-WITH-TRUNK-PASSWORD", "transport": "tls" }'JSON201 Created (excerpt) { "data": { "id": "b7e1d4c8-3f9a-4b2e-8c6d-1e5a9f3b7d2c", "name": "Twilio production", "provider": "twilio", "origination_uri": "sip:acme.sip.vollo.io;transport=tls", "termination_uri": "vollo-acme.pstn.twilio.com", "transport": "tls", "status": "active" } }The returned
origination_uriis your workspace's inbound SIP address — you'll paste it into Twilio next. -
Configure the Twilio trunk
In the Twilio console, create an Elastic SIP Trunk (say
vollo-acme) and set:Twilio setting Value Origination URI sip:acme.sip.vollo.io;transport=tls— priority 10, weight 10, enabledTermination SIP URI vollo-acme.pstn.twilio.comCredential list Username vollo-prod, the password you set above — Vollo authenticates outbound INVITEs with theseSecure trunking (TLS + SRTP) Enabled — matches "transport": "tls"Then assign your Twilio phone number(s) to that trunk under Numbers.
-
Register the number in Vollo and route it
See Phone numbers and Dispatch rules below. Call the number — your workflow should answer within a ring.
Generic SIP#
Any carrier that can send SIP to a URI works the same way: point its outbound route at your origination_uri (sip:acme.sip.vollo.io;transport=tls, port 5061; UDP/TCP on 5060 also accepted with "transport": "udp"), and create the trunk with your carrier's termination host and credentials. Vollo authenticates inbound traffic by SIP domain plus, optionally, a source-IP allowlist ("allowed_ips": ["203.0.113.0/24"] on the trunk). Media is RTP with Opus, G.711 µ-law/A-law negotiated; DTMF via RFC 4733 telephone events.
Phone numbers#
Register each E.164 number that lives on a trunk:
curl -X POST https://api.vollo.io/api/v1/phone-numbers \
-H "Authorization: Bearer pk_live_8f3KJd92mA4qL7Zx1RttVWpc" \
-H "Content-Type: application/json" \
-d '{
"number": "+12125550142",
"sip_trunk_id": "b7e1d4c8-3f9a-4b2e-8c6d-1e5a9f3b7d2c",
"project_id": "9b2e7c1a-4d3f-4e8a-9c5b-2f7d1e6a8b3c",
"label": "Main support line"
}'Numbers serve both directions: inbound calls to the number are routed by its dispatch rules, and outbound calls placed with POST /calls (or the Dial node) must use a registered number as from — it becomes the caller ID, sent out through the number's trunk.
Dispatch rules#
Dispatch rules map an inbound call to a workflow. They live on the phone number and are evaluated in ascending priority order; the first rule whose filters all match wins. A rule with no filters always matches — put one last as the catch-all.
curl -X PATCH https://api.vollo.io/api/v1/phone-numbers/f2a8c5e1-7d4b-4e9c-b3f6-8a1d5c2e7b9f \
-H "Authorization: Bearer pk_live_8f3KJd92mA4qL7Zx1RttVWpc" \
-H "Content-Type: application/json" \
-d '{
"dispatch_rules": [
{
"workflow_id": "7f3a9d2e-8c1b-4f6a-b5d4-e2c9a1f7b8d0",
"priority": 10,
"time_window": {
"days": ["mon", "tue", "wed", "thu", "fri"],
"start": "09:00",
"end": "17:00",
"timezone": "America/New_York"
}
},
{
"workflow_id": "1d6b8e3a-5c2f-4a7d-9e4b-3f8c1a6d5e2b",
"priority": 20
}
]
}'This answers business-hours calls with the support workflow and everything else with an after-hours workflow. Each rule supports:
| Field | Type | Notes |
|---|---|---|
workflow_id | string | Required. The workflow that answers. Its latest published version runs. |
priority | number | Lower evaluates first. Default 100. |
time_window | object | days (mon–sun), start/end (24-hour HH:MM, end exclusive), timezone (IANA name). Windows crossing midnight are allowed (22:00–06:00). |
caller_id | object | allow and/or block: lists of E.164 numbers or prefixes ("+9627"). Block wins over allow. Anonymous callers match the literal "anonymous". |
variables | object | Seed values merged into the workflow's variables — e.g. tag the line: {"line": "support"}. |
If no rule matches, the call is rejected with SIP 480 Temporarily Unavailable and a call.ended webhook with hangup_reason: "no_dispatch_rule".
Inbound call flow#
Outbound call flow#
Outbound calls start from the API (or from a Dial node inside a running workflow):
If the callee doesn't answer within the ring timeout, the call ends with status: "no_answer" — no workflow runs, and a call.ended webhook fires either way.
Transfer to a human#
Two nodes hand a live call to a person:
- Transfer to Human (voice category) — a warm transfer. Vollo dials the destination as a new leg, optionally plays your
announcementto the human (“Transferring a caller asking about billing”), then bridges the audio. If the human doesn't pick up, execution continues on thefailedhandle so you can apologize and take a message. - Transfer Call (telephony category) — a blind transfer via SIP REFER on the trunk. Vollo leaves the call entirely; use it to push calls into an existing PBX queue.
Destinations are templates, so they can be dynamic: tel:+12125550188 or a SIP URI like sip:support@pbx.acme.com. After a successful transfer the execution finishes with status transferred and the transcript up to the hand-off is preserved on the conversation.
DTMF#
Vollo receives keypad tones as RFC 4733 telephone events on trunk calls (and as data messages on web sessions). Collect them with the Collect DTMF node:
{
"prompt": "Please enter your six-digit account number, then press pound.",
"num_digits": 6,
"terminator": "#",
"timeout_seconds": 15,
"save_to_variable": "account_number"
}Collection ends when num_digits digits arrive, the terminator key is pressed, or the timeout elapses (routing to the timeout handle). Digits pressed while a Speak prompt is playing barge in and count. Combine with Transform → extract_digits when callers speak digits instead of typing them.