Skip to content

Integrating Elastic SIP Trunking with LiveKit

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 start

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 lk CLI 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/UDP for SIP signaling
  • 10000-20000/UDP for SIP RTP
  • 7880/TCP for the LiveKit API and WebSocket
  • 7881/TCP for LiveKit TCP ICE fallback
  • 50000-60000/UDP for LiveKit media

If you plan to use TLS later, also expose 5061/TCP.

What this validates

The quick start validates three milestones:

  1. EST can deliver inbound SIP calls to self-hosted LiveKit.
  2. LiveKit can place outbound SIP calls to EST.
  3. 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.

Quick start

Use this section for the fastest path to a working inbound and outbound test.

1. Configure Sinch EST

On the Sinch EST side:

  1. Create or reuse an EST trunk.
  2. Assign a DID to that trunk.
  3. In the Sinch Build dashboard, add a static endpoint under the trunk's inbound settings that points to your LiveKit SIP listener.
  4. Add an ACL that allows your LiveKit public IP for outbound calls from LiveKit to EST.

Example EST values:

trunk domainYOUR_EST_TRUNK_DOMAIN
static endpoint targetYOUR_LIVEKIT_PUBLIC_HOST:5060/UDP
assigned DIDYOUR_EST_DID
ACL entryYOUR_LIVEKIT_PUBLIC_IP/32

2. Configure LiveKit CLI access

export LIVEKIT_URL=http://YOUR_LIVEKIT_HOST:7880
export LIVEKIT_API_KEY=devkey
export LIVEKIT_API_SECRET=devsecretdevsecretdevsecretdevsec

lk room list

If lk room list returns successfully, your CLI access is working.

Note:

Current LiveKit releases reject very short sample API secrets, so use a sufficiently long secret.

3. Create LiveKit inbound routing

Create the LiveKit inbound trunk for your EST DID:

lk sip inbound create \
  --name sinch-est-inbound \
  --numbers YOUR_EST_DID

Save 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- \
  --randomize

Expected room pattern:

est-inbound-_<caller>_<random>

4. Test inbound calling

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 room
  • Waiting for track subscription(s)

If you see those messages, inbound SIP is working even if no agent is attached yet.

5. Create LiveKit outbound routing

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_ID

Save the returned SIPTrunkID as YOUR_OUTBOUND_TRUNK_ID.

6. Test outbound calling

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 45s

Expected result:

  • EST accepted the outbound INVITE
  • LiveKit established the SIP call
  • RTP flowed successfully

Useful success indicators:

  • Outbound SIP call established
  • accepting RTP stream

How EST maps to LiveKit

Inbound/OrginationEST trunk + DID + static endpoint -> LiveKit inbound trunk + SIP dispatch rule
Outbound/TerminationEST trunk domain + ACL or digest auth -> LiveKit outbound trunk + SIP participant create request

Next steps

After you have confirmed a working system, the following next steps can extend your solution.

Optional agent test

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.

Simple Gemini smoke test

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.

Minimal Gemini example

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_KEY

Example dependency set:

livekit-agents[google]~=1.5
python-dotenv>=1.0,<2.0

Minimal 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

Optional TLS and SRTP

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.

Optional SIP REFER transfer

LiveKit cold transfer integrates with EST via SIP REFER.

If you plan to use cold transfers, see:

Troubleshooting

The following sections provide troubleshooting tips for issues you may encounter.

No inbound INVITE reaches LiveKit

Check:

  • The EST static endpoint IP or FQDN.
  • 5060/UDP reachability.
  • Whether the public host IP changed.
  • Whether EST is pointing to the intended trunk endpoint.

The room is never created

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.

The room is created but there is no audio

If no agent is attached, this is expected.

If an agent is attached, check for:

  • received job request
  • The agent joining the room.
  • track subscribed
  • mixing track
  • accepting RTP stream

The room is created but the agent never receives a job

Check:

  • The dispatch rule includes the intended agent in roomConfig.agents if 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.

The agent does not start or exits immediately

Check:

  • LIVEKIT_URL, LIVEKIT_API_KEY, and LIVEKIT_API_SECRET are set correctly.
  • Any provider key such as GOOGLE_API_KEY is 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.

Port 8081 is already in use

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.

Outbound call fails

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.

One-way audio

Check:

  • 10000-20000/UDP for SIP RTP
  • 50000-60000/UDP for LiveKit media
  • Host NAT and firewall behavior.
  • Whether use_external_ip: true is set where needed.

CLI output looks misleading

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.

Repeated health-check or monitoring calls affect agent availability

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 available behavior.
  • 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.

Useful logs

For Docker-based self-hosting:

docker logs livekit-sip-1
docker logs livekit-livekit-1

Useful success indicators:

  • SIP participant joined room
  • Outbound SIP call established
  • track subscribed
  • mixing track
  • accepting RTP stream
  • received job request
  • registered worker

Deployment reference

Use the following examples as a minimal self-hosted reference deployment.

Example livekit.yaml

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