# LiveKit

Connect LiveKit Agents to Vonyx numbers over a SIP trunk, for outbound and inbound calls.

Point a LiveKit outbound trunk at Vonyx so your agents can dial Indian numbers, and forward your Vonyx number to your LiveKit SIP endpoint so callers reach an agent. Both directions use plain SIP over UDP or TCP.

## Before you start

- A Vonyx [SIP trunk](/docs/console/sip-trunks) with a [number](/docs/console/numbers) attached, and the trunk's username and password
- A [LiveKit Cloud](https://cloud.livekit.io/) project
- The [LiveKit CLI](https://docs.livekit.io/home/cli/) (`lk`), signed in to that project
- An agent built with [LiveKit Agents](https://docs.livekit.io/agents/start/voice-ai/), registered with an `agent_name` (explicit dispatch)

## Outbound calls

<Steps>
<Step>

### Create an outbound trunk

The outbound trunk tells LiveKit where to send the SIP INVITE and which credentials to use.

| Field | Value |
| --- | --- |
| `address` | `43.204.9.41` (hostname or IP only, no `sip:`; Vonyx listens on the default port 5060) |
| `numbers` | Your Vonyx number, `91XXXXXXXXXX`. LiveKit uses it as the caller ID. |
| `destination_country` | `in`, so LiveKit places the call from its India region |
| `auth_username` / `auth_password` | Your Vonyx trunk username and password |

```json title="outbound-trunk.json"
{
  "trunk": {
    "name": "Vonyx outbound",
    "address": "43.204.9.41",
    "numbers": ["91XXXXXXXXXX"],
    "destination_country": "in"
  }
}
```

Pass the credentials as flags so they stay out of the file:

```bash
lk sip outbound create outbound-trunk.json \
  --auth-user "your_trunk_username" \
  --auth-pass "your_trunk_password"
```

The command prints `SIPTrunkID: ST_…`. Save it. You can list trunks later with `lk sip outbound list`.

You can also create the trunk in LiveKit Cloud under **Telephony** → **SIP trunks** → **Create new trunk** → **JSON editor**, with **Trunk direction** set to **Outbound**. That editor takes the object without the `trunk` wrapper and accepts `auth_username` and `auth_password` directly.

Leave `transport` unset (auto) or set it to UDP or TCP. Don't turn on media encryption: Vonyx doesn't support TLS or SRTP.

</Step>
<Step>

### Place a test call from the CLI

Create a SIP participant. LiveKit dials `sip_call_to` through the trunk and puts the callee in `room_name`.

```json title="sip-participant.json"
{
  "sip_trunk_id": "ST_xxxxxxxx",
  "sip_call_to": "91XXXXXXXXXX",
  "room_name": "vonyx-test",
  "participant_identity": "callee",
  "participant_name": "Test callee",
  "wait_until_answered": true
}
```

```bash
lk sip participant create sip-participant.json
```

With `wait_until_answered`, the command returns once the callee picks up. This call has no agent in the room, so you only confirm that the phone rings and the call connects.

</Step>
<Step>

### Dial from your agent

In your agent's entrypoint, read the number from the job metadata and create the SIP participant in the agent's own room. This follows LiveKit's outbound example for the Python `livekit-agents` / `livekit-api` packages.

```python title="agent.py"
import json

from livekit import agents, api

# server = AgentServer(...)  # from the LiveKit voice AI quickstart

@server.rtc_session(agent_name="YOUR_AGENT_NAME")
async def entrypoint(ctx: agents.JobContext):
    phone_number = json.loads(ctx.job.metadata or "{}").get("phone_number")

    if phone_number:
        try:
            await ctx.api.sip.create_sip_participant(api.CreateSIPParticipantRequest(
                room_name=ctx.room.name,
                sip_trunk_id="ST_xxxxxxxx",      # your Vonyx outbound trunk
                sip_call_to=phone_number,        # 91XXXXXXXXXX
                participant_identity=phone_number,
                wait_until_answered=True,
            ))
        except api.SipCallError as e:
            print(f"call failed: {e.sip_status_code} {e.sip_status}")
            ctx.shutdown()
            return

        participant = await ctx.wait_for_participant(identity=phone_number)
        # session = AgentSession(...)
        # await session.start(room=ctx.room, participant=participant, agent=Assistant())
        # Let the callee speak first on outbound calls.
```

To place a different caller ID per call, set `sip_number` on the request to another of your Vonyx numbers instead of creating a new trunk.

</Step>
<Step>

### Dispatch the agent with a number

Start a job for the agent in a new room and pass the number to dial:

```bash
lk dispatch create \
  --new-room \
  --agent-name YOUR_AGENT_NAME \
  --metadata '{"phone_number": "91XXXXXXXXXX"}'
```

From a backend, use `lkapi.agent_dispatch.create_dispatch(api.CreateAgentDispatchRequest(agent_name=..., room=..., metadata=...))` instead.

</Step>
</Steps>

## Inbound calls

<Steps>
<Step>

### Find your LiveKit SIP endpoint

Your SIP URI is on the **Project settings** page in LiveKit Cloud and looks like `sip:abc123xyz.sip.livekit.cloud`. The SIP endpoint is the same value without `sip:`.

You can also build it from the CLI: run `lk project list --json`, take the `ProjectId` (for example `p_abc123xyz`) and drop the `p_` prefix.

To keep calls in India, you can use the region endpoint `abc123xyz.india.sip.livekit.cloud` instead.

</Step>
<Step>

### Create an inbound trunk

The inbound trunk accepts calls to your Vonyx number.

```json title="inbound-trunk.json"
{
  "trunk": {
    "name": "Vonyx inbound",
    "numbers": ["91XXXXXXXXXX"]
  }
}
```

```bash
lk sip inbound create inbound-trunk.json
```

Save the trunk ID it prints.

<Callout title="Restricting by IP">
LiveKit's `allowed_addresses` field could limit the trunk to `43.204.9.41`, but LiveKit must enable it for your project first (contact LiveKit support). Without it, the `numbers` list is what restricts the trunk.
</Callout>

</Step>
<Step>

### Add a dispatch rule that sends calls to your agent

The dispatch rule puts each caller in a new room (prefixed `call-`) and dispatches your agent to it through `room_config.agents`. The `agent_name` must match the name in your agent's `rtc_session` decorator.

```json title="dispatch-rule.json"
{
  "dispatch_rule": {
    "name": "Vonyx to agent",
    "trunk_ids": ["ST_inbound_xxxxxxxx"],
    "rule": {
      "dispatch_rule_individual": {
        "room_prefix": "call-"
      }
    },
    "room_config": {
      "agents": [{ "agent_name": "YOUR_AGENT_NAME" }]
    }
  }
}
```

```bash
lk sip dispatch create dispatch-rule.json
```

If you leave out `trunk_ids`, the rule matches every inbound trunk. In the agent, `ctx.job.metadata` is empty on inbound calls unless you set `metadata` on the agent entry, so the code above skips dialing and the agent can greet the caller.

</Step>
<Step>

### Point Vonyx at LiveKit

In the Vonyx console, open your trunk, turn on **Inbound calls** and set the **Inbound address** to your SIP endpoint, for example `abc123xyz.sip.livekit.cloud`. Leave out `sip:` and any user part. See [Inbound calls](/docs/console/inbound-calls).

Vonyx then forwards calls to the attached number from `43.204.9.41`.

</Step>
</Steps>

## Test the connection

1. **Outbound:** run `lk sip participant create sip-participant.json` with your own phone as `sip_call_to`. Your phone should ring with your Vonyx number as caller ID.
2. **Agent outbound:** start your agent in dev mode and run the `lk dispatch create` command above. The agent should talk once you answer.
3. **Inbound:** with the agent running, call your Vonyx number. A room named `call-…` should appear in LiveKit Cloud with a SIP participant and your agent.

The SIP participant's `sip.callStatus` attribute is `active` once a call connects, and `sip.trunkPhoneNumber` shows the number LiveKit matched on.

## Limitations

- DTMF and call transfer are not available on Vonyx yet, so don't rely on LiveKit's `dtmf` field, DTMF events or `TransferSIPParticipant`.
- New accounts make 1 call at a time ([raise limits](/docs/sip/limits)).
- No TLS or SRTP: keep LiveKit transport on UDP, TCP or auto, and media encryption off.
- Codecs are PCMU and PCMA only.
- Caller ID (`numbers` or `sip_number`) must be one of your Vonyx numbers.

## Troubleshooting

| Problem | Fix |
| --- | --- |
| `SipCallError` with `401` or `407` | The trunk username or password is wrong. Re-create the trunk with the right `--auth-user` and `--auth-pass`. |
| Outbound call rejected right away | Check the caller ID in `numbers` (or `sip_number`) is one of your Vonyx numbers and the dialled number is `91XXXXXXXXXX`. |
| `SipCallError` with `486` or `603` | The callee rejected the call. |
| `SipCallError` with `408` or `480` | No answer or the callee was unreachable. |
| Second parallel call fails | New accounts have 1 channel. See [limits](/docs/sip/limits). |
| Inbound call never reaches LiveKit | Check the inbound address has no `sip:` prefix and that **Inbound calls** is on. |
| Inbound call reaches LiveKit but is rejected | The called number didn't match the trunk's `numbers`. Add the format Vonyx sends, or check `trunk_ids` on the dispatch rule. |
| Room is created but no agent joins | The `agent_name` in the dispatch rule doesn't match the agent's `rtc_session(agent_name=...)`, or the agent isn't running. |
| Call connects with no audio | Vonyx sends RTP over UDP 10000–20000 with PCMU or PCMA only. Don't restrict LiveKit's codecs to others. See [SIP troubleshooting](/docs/sip/troubleshooting). |
