This guide explains how to connect self-hosted LiveKit to Sinch Elastic SIP Trunking (EST) for inbound and outbound calling.
This guide assumes you have the following:
- Static inbound routing from Elastic SIP Trunking to LiveKit.
- ACL-based or digest-based authentication for outbound calling from LiveKit to Elastic SIP Trunking.
Before you begin setting up your Sinch Elastic SIP trunk make sure you have done the following:
- Sign up for a free account at https://www.sinch.com/ and request access for Elastic SIP Trunking. Need help? Click here for instructions.
- If you have not already done so, configure and create your Elastic SIP trunk. Instructions on how to do this can be found here.
- Ensure you note the fully qualified domain name (FQDN) that was created on your SIP Trunk. You will need this while setting up your IP-PBX or SIP Application.
- You have a phone number assigned to your SIP Trunk.
- You have CLI access to the LiveKit host.
- The
lkCLI is installed.
After setting up your Elastic SIP Trunk infrastructure, you can validate it using these steps. For first-time validation, a public Linux host is recommended because it usually reduces local firewall, NAT, and RTP troubleshooting issues.
After changing EST ACLs, endpoints, or similar routing settings, wait at least 60 seconds before retesting.
Open these ports on the LiveKit host:
5060/UDPfor SIP signaling10000-20000/UDPfor SIP RTP7880/TCPfor the LiveKit API and WebSocket7881/TCPfor LiveKit TCP ICE fallback50000-60000/UDPfor LiveKit media
If you plan to use TLS later, also expose 5061/TCP.
The quick start validates three milestones:
- EST can deliver inbound SIP calls to self-hosted LiveKit.
- LiveKit can place outbound SIP calls to EST.
- After SIP is working, LiveKit can dispatch an agent into the inbound room and return audio to the caller.
Treat these as separate stages. Do not start with agent debugging.
Use this section for the fastest path to a working inbound and outbound test.
On the Sinch EST side:
- Create or reuse an EST trunk.
- Assign a DID to that trunk.
- In the Sinch Build dashboard, add a static endpoint under the trunk's inbound settings that points to your LiveKit SIP listener.
- Add an ACL that allows your LiveKit public IP for outbound calls from LiveKit to EST.
Example EST values:
| trunk domain | YOUR_EST_TRUNK_DOMAIN |
| static endpoint target | YOUR_LIVEKIT_PUBLIC_HOST:5060/UDP |
| assigned DID | YOUR_EST_DID |
| ACL entry | YOUR_LIVEKIT_PUBLIC_IP/32 |
export LIVEKIT_URL=http://YOUR_LIVEKIT_HOST:7880
export LIVEKIT_API_KEY=devkey
export LIVEKIT_API_SECRET=devsecretdevsecretdevsecretdevsec
lk room listIf lk room list returns successfully, your CLI access is working.
Current LiveKit releases reject very short sample API secrets, so use a sufficiently long secret.
Create the LiveKit inbound trunk for your EST DID:
lk sip inbound create \
--name sinch-est-inbound \
--numbers YOUR_EST_DIDSave the returned SIPTrunkID as YOUR_INBOUND_TRUNK_ID.
Create a dispatch rule that creates a room for each inbound caller:
lk sip dispatch create \
--name sinch-est-caller-dispatch \
--trunks YOUR_INBOUND_TRUNK_ID \
--caller est-inbound- \
--randomizeExpected room pattern:
est-inbound-_<caller>_<random>Call YOUR_EST_DID.
Expected result:
- EST delivered the INVITE to LiveKit SIP
- LiveKit matched the inbound trunk
- LiveKit matched the dispatch rule
- LiveKit created a room
- the SIP caller joined as a LiveKit participant
Useful success indicators:
SIP participant joined roomWaiting for track subscription(s)
If you see those messages, inbound SIP is working even if no agent is attached yet.
This example assumes IP-based ACL authentication on the EST side.
If you are using digest authentication instead, configure the EST trunk for digest auth and create the LiveKit outbound trunk to match that method.
lk sip outbound create \
--name sinch-est-outbound \
--address YOUR_EST_TRUNK_DOMAIN \
--transport UDP \
--numbers YOUR_EST_CALLER_IDSave the returned SIPTrunkID as YOUR_OUTBOUND_TRUNK_ID.
lk sip participant create \
--room est-outbound-test \
--identity outbound-test \
--trunk YOUR_OUTBOUND_TRUNK_ID \
--number YOUR_EST_CALLER_ID \
--call YOUR_TEST_DESTINATION \
--wait \
--timeout 45sExpected result:
- EST accepted the outbound INVITE
- LiveKit established the SIP call
- RTP flowed successfully
Useful success indicators:
Outbound SIP call establishedaccepting RTP stream
| Inbound/Orgination | EST trunk + DID + static endpoint -> LiveKit inbound trunk + SIP dispatch rule |
| Outbound/Termination | EST trunk domain + ACL or digest auth -> LiveKit outbound trunk + SIP participant create request |
After you have confirmed a working system, the following next steps can extend your solution.
Add an agent after plain SIP inbound and outbound tests pass.
Attach the agent through roomConfig.agents on the SIP dispatch rule.
This is the cleanest pattern for inbound telephony because the room is created by the SIP dispatch rule and the agent is dispatched immediately when the room is created.
Gemini is a convenient optional smoke test because getting a key is relatively easy and basic testing is low-friction, but other models can easily substitute.
Example runtime env file:
LIVEKIT_URL=ws://YOUR_LIVEKIT_HOST:7880
LIVEKIT_API_KEY=devkey
LIVEKIT_API_SECRET=devsecretdevsecretdevsecretdevsec
GOOGLE_API_KEY=YOUR_GOOGLE_API_KEYExample dependency set:
livekit-agents[google]~=1.5
python-dotenv>=1.0,<2.0Minimal agent shape used during validation:
from livekit import agents
from livekit.agents import Agent, AgentServer, AgentSession, JobContext
from livekit.plugins import google
class InboundAssistant(Agent):
def __init__(self) -> None:
super().__init__(instructions="You are a concise phone assistant.")
server = AgentServer()
@server.rtc_session(agent_name="inbound-agent")
async def inbound_agent(ctx: JobContext) -> None:
session = AgentSession(
llm=google.realtime.RealtimeModel(
model="gemini-2.5-flash-native-audio-preview-12-2025",
voice="Puck",
instructions="Greet the caller and confirm audio is working.",
),
)
await session.start(room=ctx.room, agent=InboundAssistant())
await session.generate_reply(
instructions="Answer the call and ask whether the caller can hear you."
)
if __name__ == "__main__":
agents.cli.run_app(server)Conceptual dispatch update:
{
"roomConfig": {
"agents": [
{
"agentName": "inbound-agent"
}
]
}
}Expected result:
- the agent worker registered successfully
- the inbound SIP call triggered an agent job
- the agent joined the room
- the SIP side subscribed to the agent audio track
- the caller heard the agent and the agent responded
Treat TLS and SRTP as an optional step after the basic UDP flow is working.
For EST, TLS and SRTP go together. If you switch this integration to TLS, configure it as a TLS plus SRTP setup rather than enabling TLS signaling alone.
At a minimum, confirm:
- your LiveKit SIP listener is configured for TLS
- EST is pointing its inbound static endpoint to your TLS listener, typically
YOUR_LIVEKIT_PUBLIC_HOST:5061/TLS - your certificate configuration is valid
- your LiveKit inbound and outbound SIP trunk configuration requires media encryption so SRTP is used
- your outbound LiveKit trunk transport matches the EST-side TLS configuration
If signaling connects over TLS but media does not establish, re-check the media-encryption settings on both LiveKit SIP trunks before troubleshooting deeper.
LiveKit cold transfer integrates with EST via SIP REFER.
If you plan to use cold transfers, see:
The following sections provide troubleshooting tips for issues you may encounter.
Check:
- The EST static endpoint IP or FQDN.
5060/UDPreachability.- Whether the public host IP changed.
- Whether EST is pointing to the intended trunk endpoint.
Check:
- The LiveKit inbound trunk contains the called DID.
- The SIP dispatch rule references the correct LiveKit trunk ID.
- The dispatch rule type matches your intended routing pattern.
If no agent is attached, this is expected.
If an agent is attached, check for:
received job request- The agent joining the room.
track subscribedmixing trackaccepting RTP stream
Check:
- The dispatch rule includes the intended agent in
roomConfig.agentsif you are using named dispatch. - The running worker is registered with the same agent name expected by the dispatch rule.
- You do not have a mismatch between named dispatch and automatic dispatch modes.
Typical symptoms:
- Rooms are created successfully.
- SIP logs show the call was routed.
- Agent logs never show
received job request. - Callers hear silence or the call times out.
Check:
LIVEKIT_URL,LIVEKIT_API_KEY, andLIVEKIT_API_SECRETare set correctly.- Any provider key such as
GOOGLE_API_KEYis set if you are using that provider. - LiveKit is reachable before starting the agent.
- The agent process is not failing on startup with a missing environment variable or connection error.
Typical symptom:
- Errors similar to
ws_url is required. - The agent starts, then exits immediately.
Many LiveKit agent examples expose an HTTP listener on 8081.
If startup fails with an address-in-use error, stop the old agent process and restart the intended one.
Check:
- The EST authentication method
- Whether your EST trunk is using IP-based ACL auth or digest auth.
- The EST ACL contents if using IP-based auth.
- The outbound trunk address and transport.
- Caller ID formatting in E.164.
- Whether EST accepts that caller ID on the trunk.
If UDP appears to be blocked or unreliable, try TCP as a troubleshooting step.
Check:
10000-20000/UDPfor SIP RTP50000-60000/UDPfor LiveKit media- Host NAT and firewall behavior.
- Whether
use_external_ip: trueis set where needed.
During testing, some lk sip ... list output was less reliable than isolated commands and raw --curl verification.
If output looks wrong:
- Rerun the command by itself.
- Use
lk --curl .... - Inspect service logs directly.
If you see repeated inbound SIP activity without real callers, monitoring or health-check traffic may be creating rooms and consuming worker capacity.
Typical symptoms:
- Agent workers appear busy before a real call arrives.
- Real callers hit
no agents availablebehavior. - SIP logs show repeated incomplete inbound calls.
If this happens, review worker capacity settings and confirm which inbound calls are real traffic versus monitoring traffic.
For Docker-based self-hosting:
docker logs livekit-sip-1
docker logs livekit-livekit-1Useful success indicators:
SIP participant joined roomOutbound SIP call establishedtrack subscribedmixing trackaccepting RTP streamreceived job requestregistered worker
Use the following examples as a minimal self-hosted reference deployment.
port: 7880
bind_addresses:
- "0.0.0.0"
rtc:
tcp_port: 7881
port_range_start: 50000
port_range_end: 60000
use_external_ip: true
redis:
address: 127.0.0.1:6379
keys:
devkey: devsecretdevsecretdevsecretdevsec