> ## 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 routing

> How Sayna preserves SIP destinations and fans out LiveKit webhooks to the right tenant.

Sayna keeps multi-tenant SIP traffic cleanly separated by preserving the caller’s original destination through the entire path: carrier ➝ proxy ➝ LiveKit ➝ Sayna ➝ customer webhook. This guide distills the architecture and the routing rules that keep each tenant’s events isolated.

<Info>
  Use this when you have one LiveKit SIP deployment serving many customers and need deterministic webhook delivery per domain.
</Info>

## Architecture

```mermaid theme={null}
flowchart TB
    subgraph PSTN
      A["Carrier<br/>INVITE to tenant domain"]
    end
    subgraph Edge["SIP edge"]
      B["Kamailio proxy<br/>Extracts original host<br/>Adds X-To-IP"]
      C["LiveKit SIP<br/>Creates room + participant<br/>Exposes sip.h.* attributes"]
    end
    subgraph Sayna
      D["Webhook receiver<br/>Validates LiveKit signature"]
      E["Domain resolver<br/>sip.h.x-to-ip → sip.h.to fallback"]
      F["Tenant router<br/>Signs + forwards to mapped webhook"]
    end
    subgraph Tenants
      G["Customer A webhook"]
      H["Customer B webhook"]
      I["Customer C webhook"]
    end

    A --> B
    B --> C
    C -->|participant_joined webhook| D
    D --> E
    E --> F
    F --> G
    F --> H
    F --> I
```

## End-to-end flow

<Steps>
  <Step title="Capture the intended tenant">
    The PSTN carrier sends an INVITE to the customer-facing SIP domain (for example, `customer-a.com`). Kamailio terminates the INVITE so it can inspect headers before forwarding.
  </Step>

  <Step title="Preserve destination in transit">
    Kamailio extracts the host from the Request-URI or `To` header, writes it into `X-To-IP`, and then forwards the call to LiveKit SIP. This keeps the tenant hint intact even if the `To` header is rewritten.
  </Step>

  <Step title="Expose headers inside LiveKit">
    LiveKit creates the SIP participant and exposes every SIP header as a participant attribute (`sip.h.<header-name>`), making `sip.h.x-to-ip` available for routing. Standard attributes like `sip.phoneNumber`, `sip.trunkPhoneNumber`, and `sip.callID` travel alongside.
  </Step>

  <Step title="Resolve the tenant in Sayna">
    When LiveKit emits `participant_joined`, Sayna validates the webhook signature, checks that SIP attributes exist, and extracts the routing domain with a strict priority order.
  </Step>

  <Step title="Fan out to the right webhook">
    Sayna looks up the domain in the SIP hook table, signs the payload with HMAC-SHA256, and forwards asynchronously to the configured HTTPS endpoint for that tenant. Missing or malformed domains are logged but never block LiveKit acknowledgements.
  </Step>
</Steps>

## Component responsibilities

| Component              | Role                                                                             | Notes                                                                                                           |
| ---------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| PSTN carrier           | Delivers the INVITE to your SIP edge based on DID ownership.                     | Must target the tenant-facing domain so Kamailio can capture it.                                                |
| Kamailio SIP proxy     | Adds `X-To-IP` with the original destination host before sending to LiveKit.     | Avoids losing the tenant hint when `To` is rewritten; acts as the authoritative source for routing.             |
| LiveKit SIP            | Bridges the call into a room and surfaces SIP headers as participant attributes. | Attributes are prefixed with `sip.h.`; the room name typically uses the SIP room prefix plus the called number. |
| Sayna webhook receiver | Validates LiveKit signatures and extracts routing attributes.                    | Skips non-SIP webhooks and continues even when routing data is missing, to keep LiveKit healthy.                |
| Tenant webhooks        | Consume signed events for their own calls.                                       | Each host maps to one HTTPS URL; downstream systems verify HMAC headers from Sayna.                             |

## Domain extraction rules

* Priority: `sip.h.x-to-ip` takes precedence; `sip.h.to` is the fallback when the custom header is absent.
* Parsing: hostnames are pulled from SIP URIs, display-name formats, or plain host strings; ports and parameters are discarded.
* Normalization: domains are lowercased for case-insensitive matching against the hook table.

| SIP header value                          | Parsed domain        |
| ----------------------------------------- | -------------------- |
| `sip:user@example.com`                    | `example.com`        |
| `"Display Name" <sip:user@example.com>`   | `example.com`        |
| `sip:user@example.com;user=phone;tag=xyz` | `example.com`        |
| `sips:user@secure.example.com`            | `secure.example.com` |
| `example.com:5060`                        | `example.com`        |
| `sip-1.example.com`                       | `sip-1.example.com`  |

If neither `sip.h.x-to-ip` nor `sip.h.to` can be parsed into a domain, Sayna logs the condition and skips forwarding.

## Webhook fan-out behavior

* **Signature verification**: LiveKit webhooks must pass JWT validation before routing begins.
* **SIP-only enforcement**: Events without SIP attributes are acknowledged but never forwarded.
* **Hook lookup**: Domains are matched against the configured hook list (case-insensitive). Missing entries produce warnings rather than errors.
* **Payload signing**: Forwarded requests include `X-Sayna-Signature`, `X-Sayna-Timestamp`, `X-Sayna-Event-Id`, and `X-Sayna-Signature-Version` for downstream verification.
* **Asynchronous delivery**: Forwarding runs off the main webhook handler to keep LiveKit responses fast; retries depend on downstream HTTP results and your operational policies.

### Payload shape (conceptual)

| Field                                                         | Meaning                                                                             |
| ------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
| `participant.name`, `participant.identity`, `participant.sid` | Identifiers for the SIP caller inside the LiveKit room.                             |
| `room.name`, `room.sid`                                       | The LiveKit room created for the call, typically prefixed with the SIP room prefix. |
| `from_phone_number`                                           | The caller’s number from the SIP attributes.                                        |
| `to_phone_number`                                             | The called DID that triggered this room.                                            |
| `room_prefix`                                                 | Configured SIP room prefix to distinguish SIP traffic.                              |
| `sip_host`                                                    | The domain used to choose the webhook destination.                                  |

## Hook model and lifecycle

* **Mapping**: Each hook entry pairs a host (tenant domain) with an HTTPS webhook URL and an `auth_id`; hostnames must be unique.
* **Room ownership**: The `auth_id` field determines who owns rooms created from inbound SIP calls. When a call arrives, Sayna sets `room.metadata.auth_id` to the hook's `auth_id` value.
* **Sources of truth**: Hooks load from configuration at startup and can be updated at runtime through the SIP hook APIs; runtime changes persist across restarts.
* **Security**: HTTPS is required, and all forwarded calls are HMAC-signed so tenants can authenticate Sayna.
* **Operations**: Adding, replacing, or removing a host takes effect immediately for new events; existing in-flight calls keep their resolved destination.

<Note>
  When `AUTH_REQUIRED=true`, the `auth_id` field is required for each SIP hook entry. This ensures that inbound SIP calls are properly associated with a tenant for room access control.
</Note>

## Reliability checklist

* Confirm Kamailio always stamps `X-To-IP` so the strongest routing signal is present.
* Monitor webhook forwarding logs for “no webhook configured for domain” and align hook entries with the domains your carriers use.
* Validate `sip.h.to` parsing in LiveKit traces when troubleshooting unexpected skips.
* Keep hook secrets long and rotate them with tenants periodically.
* Track downstream failures (timeouts, TLS issues, DNS resolution) since Sayna will log but not retry indefinitely without operator action.
