An inbound call starts when someone dials one of your numbers. The carrier sends a SIP INVITE to LiveKit SIP, the number's dispatch rule creates a room, and the API picks an agent and dispatches the voice worker into the room. This page covers how the agent is chosen and the settings that control who gets through.
Assign an inbound agent#
Open the number#
Go to Phone Numbers and select the number in the list.
Pick the agent#
Under Inbound Call Agent, choose an agent in Call Agent. Choose None (disable inbound) to stop answering on this number.
Optional: split traffic#
Turn on A/B Testing next to Call Agent to split calls across two or more agents by weight. See A/B testing.
Changes save as soon as you pick a value. With the API, send inbound_agents to PATCH /update-phone-number/:n:
curl -X PATCH "https://api.tried.com.au/update-phone-number/%2B61255501234" \
-H "Authorization: Bearer $TRIED_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "inbound_agents": [{ "agent_id": "agent_8e0c...", "weight": 1 }] }'URL-encode the + in the path as %2B.
What happens on each inbound call#
- LiveKit SIP matches the dialled number to the number's inbound trunk. The dispatch rule creates a room with the prefix
call-and theparticipant_joinedwebhook reaches the API. - The API finds the number from the SIP
Touser (a leading+is optional) and the caller from the SIPFrom. - If
allowed_inbound_country_listis set and the caller's number is not in one of those countries, the call is hung up. - An agent is picked from
inbound_agentsby weight. - If the number has an
inbound_webhook_url, Tried posts acall_inboundevent and applies the response (override agent, variables, metadata, or reject). See Inbound call webhook. - If the number is bound to an environment tag or version under Agent version, that version is used, unless the weights or the webhook already chose a version. See Environments.
- The agent's pre-call workflow runs, if it has one. Its outputs become dynamic variables.
- A concurrency slot is checked. If one is free, the call is created and the agent joins. If not, the caller is held (see below).
LiveKit can deliver participant_joined more than once for the same room. Tried handles one run per room, so a duplicate never dispatches a second agent.
Inbound webhook#
Tick Add an inbound webhook under Inbound Call Agent and enter an https:// URL to call your server before each call. Use it to look up the caller in your CRM, choose an agent, pass dynamic variables, or reject spam.
- Timeout: 10 seconds per attempt.
- Retries: 2, with a short backoff (
TRIED_INBOUND_WEBHOOK_RETRY_MS, 500 ms by default, multiplied by the attempt number). - Every attempt carries the same pre-assigned
call_id, which becomes the call's ID. - If every attempt fails or the response is not JSON, the call continues with the number's own agent.
The payload and response format are on the Inbound call webhook page.
Restrict caller countries#
Allowed Inbound Countries limits which callers reach the agent. The dashboard picker offers 12 countries (Australia, New Zealand, United States, Canada, United Kingdom, Ireland, Singapore, India, Philippines, Germany, France, Japan) and All countries allowed. The API field allowed_inbound_country_list accepts any ISO 3166 two-letter codes:
{ "allowed_inbound_country_list": ["AU", "NZ"] }The caller's country is worked out from the number with libphonenumber. Send null to allow every country again.
When every line is busy#
Inbound calls are admitted against the workspace concurrency limit. Inbound calls may use the reserved inbound slots that outbound and web calls cannot. When there is still no free slot:
- The caller is held and the call is marked queued.
- The API polls for a slot every second for up to 40 seconds (
INBOUND_QUEUE_TIMEOUT_MS,INBOUND_QUEUE_POLL_MS). - If a slot frees up, the agent is dispatched.
- If not, and the number has a Fallback number, the call is transferred there over SIP. The call ends with
disconnection_reason: "call_transfer"and recordstransfer_destination. - Otherwise the caller is hung up.
Set the fallback number under Routing and SIP on the number, or with fallback_number in the API. It must be E.164. The fallback transfer needs a real LiveKit SIP connection. Needs keys
Caller information on the call#
The call object for an inbound call has:
| Field | Value |
|---|---|
direction |
"inbound" |
from_number |
The caller, in E.164 when the carrier sends digits only |
to_number |
Your number |
metadata.sip_headers |
X-* headers and User-to-User, Diversion, History-Info, P-Asserted-Identity from the INVITE. See SIP headers. |
stir_shaken_attestation |
A, B or C when the carrier signed the call. See Caller ID and trust. |
caller_cnam |
Caller name from the carrier, when present |
concurrency_burst |
true when the call was admitted above the base limit |
The caller is also linked to a built-in contact (contact_id), created on first contact.
Reasons an inbound call is refused#
| Situation | Result |
|---|---|
| Dialled number is not in any workspace | Room closed, no call record |
| Caller country not allowed | Hung up |
| No inbound agent and the webhook did not set one | Hung up |
Webhook returned reject: true |
Hung up |
| Agent or version not found | Hung up |
| No slot within 40 seconds and no fallback | Hung up |
Test it#
- Call the number from your mobile. The agent should answer within a couple of rings.
- In mock mode there is no SIP server, so use the agent builder Test Audio for audio and
scripts/sip-smoke.shfor a real SIP call through locallivekit-serverandlivekit-sip.