> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sayna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# SIP configuration

> Opt into SIP-aware routing, provisioning, and webhook forwarding for LiveKit calls.

Sayna treats SIP support as a fully optional module. When the `sip` block is absent, no trunks are provisioned, webhook forwarding is skipped, and the runtime avoids emitting SIP-specific logs. Configure the section below only when you need SIP-enabled LiveKit rooms.

<Info>
  LiveKit SIP support requires valid `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET` values. Provisioning is idempotent—restarts never duplicate trunks or dispatch rules.
</Info>

## Configuration methods

### YAML example

```yaml theme={null}
sip:
  room_prefix: "sip-"
  allowed_addresses:
    - "192.168.1.0/24"
    - "10.0.0.1"
  hooks:
    - host: "example.com"
      url: "https://webhook.example.com/events"
      auth_id: "tenant-123"
    - host: "another.com"
      url: "https://webhook2.example.com/events"
      auth_id: "tenant-456"
```

### Environment variables

| Variable                | Required | Description                                                                                                                                      |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `SIP_ROOM_PREFIX`       | Yes      | Prefix every SIP-related LiveKit room must start with (alphanumeric, `-`, `_`).                                                                  |
| `SIP_ALLOWED_ADDRESSES` | No       | Comma-separated IPv4 addresses/CIDRs. Whitespace is trimmed.                                                                                     |
| `SIP_HOOKS_JSON`        | No       | JSON array of `{ "host": "...", "url": "https://...", "auth_id": "..." }` objects. HTTPS required; `auth_id` required when `AUTH_REQUIRED=true`. |

```bash theme={null}
export SIP_ROOM_PREFIX="sip-"
export SIP_ALLOWED_ADDRESSES="192.168.1.0/24,10.0.0.1"
export SIP_HOOKS_JSON='[{"host":"example.com","url":"https://webhook.example.com/events","auth_id":"tenant-123"}]'
```

### Precedence

```
Environment variables > YAML file > (feature disabled)
```

If a value is supplied via env vars it replaces the YAML counterpart entirely (lists are not merged).

## Validation rules

<Columns cols={3}>
  <Card title="Room prefix">
    * Cannot be empty
    * Only alphanumeric, `-`, `_`
    * Examples: `sip-`, `room_42`
  </Card>

  <Card title="Allowed addresses">
    * List cannot be empty when SIP block exists
    * Each entry must resemble IPv4 or CIDR (`X.X.X.X/Y`)
    * IPv6 not supported yet
  </Card>

  <Card title="Hooks">
    * Hostnames must be unique (case-insensitive)
    * URLs must start with `https://`
    * Rejects insecure `http://` hooks
    * `auth_id` required when `AUTH_REQUIRED=true`
  </Card>
</Columns>

Sayna validates the block at startup and fails fast with descriptive errors so you never run half-configured SIP resources.

## Auto-provisioning workflow

When SIP config and LiveKit credentials are present, Sayna provisions everything automatically during startup.

<Steps>
  <Step title="Trunk creation">
    Creates `sayna-{room_prefix}-trunk` with your `allowed_addresses` list.
  </Step>

  <Step title="Dispatch rule">
    Creates `sayna-{room_prefix}-dispatch` that routes SIP calls into LiveKit rooms matching the prefix (max 3 participants by default).
  </Step>

  <Step title="Idempotent guards">
    Existing resources are reused; provisioning failures abort startup with clear logs so you can fix credentials or LiveKit connectivity issues.
  </Step>
</Steps>

**Required credentials**

* `LIVEKIT_API_KEY`
* `LIVEKIT_API_SECRET`
* Valid `sip` block or env vars

**Observability**

```text theme={null}
INFO SIP configuration detected, provisioning LiveKit SIP trunk and dispatch rules
INFO Successfully provisioned SIP resources: trunk=sayna-sip--trunk, dispatch=sayna-sip--dispatch
```

Missing credentials simply log an info and skip provisioning; errors panic with the trunk/dispatch names for easier debugging. Verify resources via the LiveKit UI under **SIP → Inbound Trunks / Dispatch Rules**.

## Runtime behavior

### Room prefix matching

Any LiveKit room whose name begins with `room_prefix` is treated as SIP. This heuristic drives routing logic, logging, and webhook forwarding.

### IP filtering

`allowed_addresses` becomes the allowlist enforced by LiveKit. Use it to restrict inbound SIP traffic to trusted carriers or local networks.

### Webhook forwarding

When LiveKit delivers webhooks, Sayna optionally forwards the **exact** JSON payload to downstream HTTPS hooks based on the SIP `To` header:

1. Inspect `participant.attributes["sip.h.to"]` (populated by LiveKit SIP).
2. Parse the host portion (e.g., `sip:calls@sip1.example.com` → `sip1.example.com`).
3. Match the host to entries in `hooks` (case-insensitive).
4. Post the payload to `hook.url` using the shared `ReqManager` so pooling/limits match the rest of the platform.
5. If no hook matches, log the omission but still respond `200 OK` to LiveKit.

Forwarding runs asynchronously so LiveKit acknowledgements stay fast. With no hooks configured, the handler no-ops.

### Per-request configuration overrides

When initiating outbound SIP calls via the `/sip/call` endpoint, you can override specific SIP settings on a per-request basis without changing the global configuration. This is useful for multi-provider scenarios or when different calls require different credentials.

<Tip>
  Per-request overrides take priority over global server configuration. If neither the request nor global config provides an `outbound_address`, the call will fail.
</Tip>

See the [SIP call API reference](/api-reference/endpoint/sip-call) for full details on the `sip` configuration object and supported fields.

### Runtime hook management

You can inspect and update the forwarding table without restarting the server:

* `GET /sip/hooks` returns the cached list of `{ host, url, auth_id }` entries.
* `POST /sip/hooks` replaces hooks with matching hosts (case-insensitive) and adds new ones, then persists the result. The `auth_id` field determines room ownership for inbound calls. Runtime additions reuse the global hook secret; per-hook secrets are not stored.

Both endpoints respect `AUTH_REQUIRED=true` if you enforce auth globally. Always use unique hosts and HTTPS URLs or the request will be rejected.

## Reference configurations

**Single carrier**

```yaml theme={null}
sip:
  room_prefix: "sip-"
  allowed_addresses:
    - "203.0.113.0/24"
  hooks:
    - host: "sip-provider.example.com"
      url: "https://backend.myapp.com/sip/events"
      auth_id: "tenant-main"
```

**Multiple carriers**

```yaml theme={null}
sip:
  room_prefix: "sip-"
  allowed_addresses:
    - "203.0.113.0/24"
    - "198.51.100.0/24"
  hooks:
    - host: "provider-a.example.com"
      url: "https://backend.myapp.com/sip/provider-a"
      auth_id: "tenant-a"
    - host: "provider-b.example.com"
      url: "https://backend.myapp.com/sip/provider-b"
      auth_id: "tenant-b"
```

**Local testing**

```yaml theme={null}
sip:
  room_prefix: "test-sip-"
  allowed_addresses:
    - "127.0.0.1"
    - "192.168.1.0/24"
  hooks:
    - host: "localhost"
      url: "https://localhost:8443/webhooks"
      auth_id: "dev-tenant"
```

## Accessing config in code

```rust theme={null}
use sayna::config::ServerConfig;

fn main() -> anyhow::Result<()> {
    let config = ServerConfig::from_env()?;

    if let Some(sip) = &config.sip {
        tracing::info!(prefix = %sip.room_prefix, count = sip.hooks.len(), "SIP enabled");
        for hook in &sip.hooks {
            tracing::debug!(
                host = %hook.host,
                url = %hook.url,
                auth_id = %hook.auth_id,
                "Configured SIP hook"
            );
        }
    } else {
        tracing::info!("SIP disabled");
    }

    Ok(())
}
```

## Troubleshooting

| Error                                                           | Cause                                                       | Fix                                           |
| --------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------- |
| `SIP room_prefix is required when SIP configuration is present` | Allowed addresses or hooks were specified without a prefix. | Set `SIP_ROOM_PREFIX` or the YAML equivalent. |
| `SIP hook URL must be HTTPS`                                    | Hook used `http://`.                                        | Update to `https://` for every entry.         |
| `Duplicate SIP hook host`                                       | Same host appears twice (case-insensitive).                 | Ensure each host is unique.                   |
| `SIP allowed_address '...' does not look valid`                 | Invalid IPv4/CIDR string.                                   | Replace with real IPv4 or `x.x.x.x/y` CIDR.   |

## Related docs

* [SIP call API](/api-reference/endpoint/sip-call) – initiate outbound SIP calls with optional per-request configuration overrides.
* [Configure Twilio SIP](/guides/sip-twilio) – walk through Twilio Elastic SIP Trunking setup using Sayna's SIP endpoint.
* [Authentication](/guides/authentication) – protect webhook receivers and REST APIs.
* [Deployment guide](/guides/deployment) – configure LiveKit credentials alongside SIP env vars.

## Future enhancements

Planned improvements include IPv6 allowlists, configurable webhook retry policies, wildcard host matching, payload signing, and per-hook credentials. Track release notes for updates.
