LiveKit Cloud
LiveKit Cloud supplies the media (SIP bridge + WebRTC) in v1. A self-hosted SFU is a later milestone.
1. Project + keys
- Sign up at cloud.livekit.io.
- Create a project.
- From Settings → Keys, copy these values into
.env:LIVEKIT_URL—wss://<project>-<region>.livekit.cloudLIVEKIT_API_KEYLIVEKIT_API_SECRET
2. SIP outbound trunk
First create the Twilio trunk and credentials in the Twilio guide. Then, in LiveKit Cloud:
- Open Telephony → SIP trunks → Create new trunk.
- Select Outbound and use the Twilio termination domain
(
<name>.pstn.twilio.com) as the address. - Add the Twilio number in E.164 format and enter the same username/password configured on the Twilio trunk.
- Create the trunk and copy its ID into
.envasLIVEKIT_TWILIO_SIP_OUTBOUND_TRUNK_ID.
Hail passes this ID to LiveKit for every outbound call from a Twilio number.
The older LIVEKIT_SIP_OUTBOUND_TRUNK_ID name remains supported as a
compatibility alias; the explicit Twilio name takes precedence. Numbers from
another carrier use their own trunk: see DIDWW and
LIVEKIT_TELNYX_SIP_OUTBOUND_TRUNK_ID. Hail does not read a Twilio
trunk-domain environment variable. See LiveKit's
outbound trunk reference
for the current UI and JSON forms.
3. Voicebot worker
With the local Compose overlay, run:
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d voicebotAt startup, the worker registers with LiveKit as a dispatchable agent. The Hail API dispatches it into a room for each call.
For the full flow, refer to Architecture.
4. Inbound calls
Hail answers calls on a number once the number routes calls to an agent (Agents). LiveKit needs one inbound trunk and one dispatch rule. Hail adds and removes numbers on the trunk itself.
-
Inbound trunk. One trunk for every carrier: LiveKit allows a single wildcard (empty
numbers) inbound trunk per project. Withoutnumbers, LiveKit needsallowed_addresses(orauth_username/auth_password, which Twilio Elastic SIP does not send). The carrier pages say what each carrier sends: Twilio, Telnyx, DIDWW.cat > inbound.json <<'JSON' {"trunk": {"name": "hail-inbound", "numbers": [], "allowed_addresses": ["0.0.0.0/0"]}} JSON lk sip inbound create inbound.json # → ST_... → LIVEKIT_SIP_INBOUND_TRUNK_IDHail reads the dialed number from the call and finds its carrier in
phone_numbers; the trunk does not need to know it. To lock each carrier to its own trunk instead, create one trunk per carrier with that carrier's signaling IPs inallowed_addressesand setLIVEKIT_<CARRIER>_SIP_INBOUND_TRUNK_ID(an override not listed in.env.example) to that trunk's id. Narrow0.0.0.0/0to the carriers' IP ranges when you can. -
Dispatch rule. One rule, bound to the inbound trunk, that puts each caller in its own room and dispatches the voicebot with the static metadata it expects:
cat > dispatch-rule.json <<'JSON' { "dispatch_rule": { "name": "hail-inbound", "trunk_ids": ["<inbound-trunk-id>"], "rule": {"dispatchRuleIndividual": {"roomPrefix": "hail-in-"}}, "roomConfig": { "agents": [{"agentName": "hail-voicebot", "metadata": "{\"direction\":\"inbound\"}"}] } } } JSON lk sip dispatch create dispatch-rule.jsonRoom names include the caller's number (LiveKit's individual rule);
calls.from_e164records it anyway. -
Set the trunk id in
.envand recreateapiandvoicebot. ThenPATCH /v1/numbers/{id}withvoice_agent_idregisters a number (Agents).
Reference: LiveKit accepting calls, inbound trunk, dispatch rule.
VM deployment
Self-host Hail on one Ubuntu VM behind HTTPS. GitHub Actions deploys every commit on main. The database is managed (Neon, Supabase, RDS, or your choice). The VM runs only the stateless services.
Twilio
You need a Twilio account, a phone number, and a SIP trunk that bridges to LiveKit Cloud.