Skip to content
TriedDocs
Deploy

Inbound calls

Route callers on a phone number to an agent, override the agent per call with a webhook, and decide what happens when every line is busy.

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.

A number with a 70/30 inbound split, an inbound webhook and Australia and New Zealand as allowed inbound countries

What happens on each inbound call#

  1. LiveKit SIP matches the dialled number to the number's inbound trunk. The dispatch rule creates a room with the prefix call- and the participant_joined webhook reaches the API.
  2. The API finds the number from the SIP To user (a leading + is optional) and the caller from the SIP From.
  3. If allowed_inbound_country_list is set and the caller's number is not in one of those countries, the call is hung up.
  4. An agent is picked from inbound_agents by weight.
  5. If the number has an inbound_webhook_url, Tried posts a call_inbound event and applies the response (override agent, variables, metadata, or reject). See Inbound call webhook.
  6. 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.
  7. The agent's pre-call workflow runs, if it has one. Its outputs become dynamic variables.
  8. 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:

  1. The caller is held and the call is marked queued.
  2. The API polls for a slot every second for up to 40 seconds (INBOUND_QUEUE_TIMEOUT_MS, INBOUND_QUEUE_POLL_MS).
  3. If a slot frees up, the agent is dispatched.
  4. 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 records transfer_destination.
  5. 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

Routing and SIP card with fallback number, calls per second and a successful SIP connection test

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.sh for a real SIP call through local livekit-server and livekit-sip.