Use this when you have one LiveKit SIP deployment serving many customers and need deterministic webhook delivery per domain.
Architecture
End-to-end flow
1
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.2
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.3
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.4
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.5
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.
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-iptakes precedence;sip.h.tois 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:[email protected] | example.com |
"Display Name" <sip:[email protected]> | example.com |
sip:[email protected];user=phone;tag=xyz | example.com |
sips:[email protected] | secure.example.com |
example.com:5060 | example.com |
sip-1.example.com | sip-1.example.com |
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, andX-Sayna-Signature-Versionfor 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; hostnames must be unique.
- 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.
Reliability checklist
- Confirm Kamailio always stamps
X-To-IPso 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.toparsing 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.