Tried answers and places calls over SIP through LiveKit SIP. Any carrier or PBX that can send SIP to a hostname can bring its numbers: Twilio Elastic SIP Trunking, Telnyx, your own carrier, or an on-premise PBX. You keep the number with your carrier; Tried only needs to receive its calls and to send outbound calls back through your trunk.
There are three ways to connect:
| Method | Use when | How |
|---|---|---|
| Import a number | Each number has its own trunk settings | Connect via SIP trunking, or POST /import-phone-number |
| Shared SIP trunk | Many numbers on one carrier trunk | POST /create-sip-trunk, then import numbers with sip_trunk_id (API only) |
| Register call | Your PBX decides per call which agent answers | POST /v2/register-phone-call. See Register call. |
Real SIP needs LiveKit: LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET and LIVEKIT_SIP_URI (the SIP host your carrier dials, for example <project>.sip.livekit.cloud or your own livekit-sip hostname), plus the LiveKit webhook at https://<api>/internal/livekit/webhook. Without them, provisioning is recorded in the provider operation log and nothing is sent.
The two directions#
Inbound (origination). Your carrier sends calls for the number to Tried. Point the number's route at:
sip:<number>@<LIVEKIT_SIP_URI>GET /sip-origination returns the values for your deployment: sip_host, origination_uri (sip:<host>;transport=tcp) and ip_ranges, the address ranges our SIP traffic comes from, for your trunk's allow list.
Outbound (termination). Tried sends calls to your carrier's termination URI, authenticated with a SIP username and password if your carrier needs them. The caller ID is the number itself.
Import a number#
Open the dialog#
On Phone Numbers, click + and Connect via SIP trunking.
Enter the number and trunk#
Phone Number in E.164. Termination URI is your carrier's SIP host, for example sip.provider.com, optionally with a port. Choose Transport (TCP, UDP or TLS) and Media encryption.
Test the trunk#
Click Test SIP connection. Tried sends one SIP OPTIONS request to the termination URI and shows whether it answered, the status and the round trip time.
Add credentials and options#
SIP username and SIP password for outbound calls. Optional: Fallback number, Calls per second (up to 150 on your own trunk), Nickname (optional) and Inbound agent (optional).
Save#
Click Save. The number is provisioned on LiveKit SIP.
Route the number at your carrier#
Send the number's inbound calls to sip:<number>@<LIVEKIT_SIP_URI>.
Import with the API#
curl -X POST https://api.tried.com.au/import-phone-number \
-H "Authorization: Bearer $TRIED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone_number": "+61290001234",
"termination_uri": "sip.carrier.example.com",
"sip_trunk_auth_username": "tried",
"sip_trunk_auth_password": "a-strong-password",
"transport": "TLS",
"media_encryption": "required",
"allowed_inbound_addresses": ["203.0.113.10/32"],
"inbound_agents": [{ "agent_id": "agent_8e0c...", "weight": 1 }]
}'| Field | Type | Default | Description |
|---|---|---|---|
phone_number |
string | required | E.164. |
termination_uri |
string | required unless sip_trunk_id |
Carrier SIP host, optionally host:port. |
sip_trunk_auth_username |
string | none | Username for outbound calls. |
sip_trunk_auth_password |
string | none | Password for outbound calls. Stored encrypted (AES-GCM) and never returned. |
transport |
TCP | UDP | TLS |
TCP |
Signalling transport. |
media_encryption |
disabled | allowed | required |
allowed |
SRTP. allowed uses SRTP when the other side offers it; required refuses plain RTP. |
allowed_inbound_addresses |
string[] | none | IPs or CIDRs allowed to send INVITEs for this number. |
sip_trunk_id |
string | none | Put the number on a shared workspace trunk instead. |
provider |
custom | twilio |
detected | twilio is set automatically when the termination URI is a *.pstn.twilio.com host. |
fallback_number |
E.164 | none | Where inbound calls go when no slot frees up. |
cps_limit |
number, 0.1 to 150 | 10 | Outbound calls per second. |
nickname, inbound_agents, outbound_agents, inbound_webhook_url, allowed_inbound_country_list, allowed_outbound_country_list |
Same as on bought numbers. |
The response is HTTP 201 with the number. Imported numbers have provider: "custom" (or "twilio").
What Tried provisions#
Importing, buying, updating or deleting a number creates, updates or deletes its LiveKit objects through the LiveKit server SDK. You never run lk sip commands.
| Object | Contents |
|---|---|
| Inbound trunk | The number, allowedAddresses from allowed_inbound_addresses, media encryption, Krisp noise cancellation, all X- headers plus User-to-User, Diversion, History-Info and P-Asserted-Identity mapped to attributes, and BYE header mapping |
| Outbound trunk | Address from the termination URI, transport, username and password, media encryption |
| Dispatch rule | One room per call with the prefix call-, and the attributes tried.phone_number and tried.workspace_id |
The number records the IDs in livekit_inbound_trunk_id, livekit_outbound_trunk_id and livekit_dispatch_rule_id, and the result in provisioning_status:
| Status | Meaning |
|---|---|
pending |
Created, provisioning not finished |
provisioned |
All objects exist |
error |
Something failed; provisioning_error has the message |
POST /provision-phone-number/:n re-applies everything. Changing termination_uri, credentials, transport, media_encryption, allowed_inbound_addresses or sip_trunk_id with PATCH /update-phone-number/:n replaces the trunks in place.
Shared SIP trunks#
When many numbers use the same carrier trunk, create one shared trunk and attach the numbers to it. One inbound trunk, one outbound trunk and one dispatch rule serve all of them. This is API only; the dashboard imports one number at a time.
curl -X POST https://api.tried.com.au/create-sip-trunk \
-H "Authorization: Bearer $TRIED_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Head office PBX",
"termination_uri": "pbx.example.com:5061",
"transport": "TLS",
"media_encryption": "allowed",
"allowed_inbound_addresses": ["203.0.113.10/32"],
"inbound_auth_username": "pbx",
"inbound_auth_password": "another-strong-password"
}'| Field | Default | Description |
|---|---|---|
name |
SIP trunk |
Label. |
termination_uri |
required | Outbound target. |
sip_trunk_auth_username, sip_trunk_auth_password |
none | Outbound credentials. |
transport |
TCP |
TCP, UDP or TLS. |
media_encryption |
allowed |
disabled, allowed or required. |
allowed_inbound_addresses |
[] |
IPs or CIDRs allowed to send INVITEs. |
inbound_auth_username, inbound_auth_password |
none | Digest credentials your carrier must present on inbound INVITEs. |
Then import each number with sip_trunk_id and no termination_uri. The trunk's number list stays in sync. When the last number leaves the trunk, its LiveKit objects are removed, because an inbound trunk with no numbers would catch every call. Other endpoints: GET /list-sip-trunks, GET /get-sip-trunk/:id, PATCH /update-sip-trunk/:id, DELETE /delete-sip-trunk/:id.
Test the SIP connection#
POST /test-sip-connectionwith{ termination_uri, transport }tests a trunk before you import.POST /test-phone-number-connection/:ntests a saved number (its trunk, its shared trunk, orsip.telnyx.comfor Telnyx numbers).
Both send one SIP OPTIONS over UDP, TCP or TLS and return { reachable, status_code, reason, latency_ms }. In mock mode the result is simulated (200 OK (mock)) and the dashboard says (simulated, no SIP server configured). The same test is on each number's Routing and SIP card.
Codecs and media#
LiveKit SIP negotiates PCMU, PCMA and G.722. Caller keypad presses arrive as RFC 2833 telephone events, and the agent's press_digit tool sends RFC 2833 events.
Local testing without keys#
scripts/sip-smoke.sh runs livekit-server and livekit-sip locally with the API and the worker, and places real SIP calls with a small SIP client (PCMU and RFC 4733 DTMF). No vendor keys or root access needed.