Guide

Moving a LiveKit trunk from Twilio to RingTrunk

A migration guide for developers who already run LiveKit with Twilio Elastic SIP Trunking and want an Indian phone number. Which LiveKit objects stay the same, which two fields change, and which Indian rules apply that Twilio never asked you about.

Updated 6 September 2026

In short: Moving a LiveKit trunk from Twilio to RingTrunk changes two fields on the outbound trunk and the allowed address on the inbound trunk; the agent code stays the same.

If your LiveKit project already places and receives calls through Twilio Elastic SIP Trunking, you already know how RingTrunk works. Both are SIP trunks with the same shape: an address to authenticate to for outbound calls, a source to trust for inbound calls, numbers attached to the trunk, and credentials. This page lists exactly what changes and what does not.

What stays the same

  • The LiveKit objects. One outbound trunk, one inbound trunk, one dispatch rule, created with lk or the LiveKit API.
  • Your agent code. Rooms, participants, create_sip_participant, webhooks: nothing changes.
  • The inbound model. The provider's INVITE carries no credentials and is trusted by source IP, on Twilio and on RingTrunk alike.
  • The response conventions. Rejections come back as SIP responses with a reason you can read.

What changes

FieldTwilioRingTrunk
Outbound trunk address<your-domain>.pstn.twilio.compstn.ringtrunk.com
Outbound trunk transportas configuredSIP_TRANSPORT_TCP, pinned
Outbound trunk numbersyour Twilio numbersyour Indian number, 10 digits, for example 9XXXXXXXXX
Outbound trunk auth_usernameyour credential list usernameyour trunk id, TRxxxxxxxxxxxxxxxx
Inbound trunk allowed_addressesTwilio's IP rangesRingTrunk's source IP, shown on your trunk's Origination tab
Inbound trunk numbersE.164four forms: 9XXXXXXXXX, +919XXXXXXXXX, 09XXXXXXXXX, 919XXXXXXXXX
Where inbound calls are routedTwilio console origination URIRingTrunk portal, the trunk's Origination tab

Two lines in the outbound trunk, the address and the numbers, are the whole migration for outbound. For inbound, swap the allowed address and list the number in the four forms.

1. Edit the outbound trunk

outbound-trunk.json
cat > outbound-trunk.json <<'EOF'
{
  "trunk": {
    "name": "RingTrunk outbound",
    "address": "pstn.ringtrunk.com",
    "transport": "SIP_TRANSPORT_TCP",
    "numbers": ["9XXXXXXXXX"],
    "auth_username": "TRxxxxxxxxxxxxxxxx",
    "auth_password": "YOUR_SIP_PASSWORD"
  }
}
EOF
lk sip outbound create outbound-trunk.json

Pin the transport to TCP. Left on automatic, a trunk may ignore the SRV port and try 5060, which will not answer you.

2. Edit the inbound trunk

inbound-trunk.json
cat > inbound-trunk.json <<'EOF'
{
  "trunk": {
    "name": "RingTrunk inbound",
    "numbers": ["9XXXXXXXXX", "+919XXXXXXXXX", "09XXXXXXXXX", "919XXXXXXXXX"],
    "allowed_addresses": ["<RINGTRUNK_SOURCE_IP>"]
  }
}
EOF
lk sip inbound create inbound-trunk.json

Keep auth_username and auth_password off this object, exactly as with Twilio. Credentials on an inbound trunk make every call fail with 401.

3. Keep the dispatch rule

Your existing dispatch rule works unchanged. If you dispatch LiveKit Agents through room_config.agents, keep it; a webhook-driven worker needs nothing.

4. Point RingTrunk at LiveKit

In the RingTrunk portal, set the trunk's origination URI to your project's SIP endpoint, <project-id>.sip.livekit.cloud:5060;transport=tcp, or <project-id>.india.sip.livekit.cloud to keep rooms in Mumbai.

Rules that are different in India

Three things Twilio never asked you about apply on every call:

  • Caller ID must be one of your own attached numbers. Any other From number is rejected with 403 cli_not_allowed. This is an Indian regulatory requirement, enforced on every call, so a "spoof the customer's number" pattern will not work.
  • Destinations are Indian mobile numbers only. 10 digits starting 6, 7, 8 or 9. Landlines, short codes and international destinations are rejected with 403 destination_not_supported.
  • The codec is G.711 A-law (PCMA), 20 ms. Offering only G.729 or Opus fails with 488 Not Acceptable Here.

Placing a call after the switch

bash
lk sip participant create \
  --trunk <YOUR_RINGTRUNK_OUTBOUND_TRUNK_ID> \
  --call +91XXXXXXXXXX \
  --number 9XXXXXXXXX \
  --room my-room

--number is now your Indian number. Everything else in the command is the same as before.

Running both at once

Nothing stops a LiveKit project from holding a Twilio trunk for one set of numbers and a RingTrunk trunk for Indian numbers. Trunks are selected per call by sip_trunk_id, so an agent can dial an Indian customer on RingTrunk and a US customer on Twilio from the same code.

See Connect LiveKit Cloud for the full setup from scratch and Troubleshooting for every SIP response.