Skip to main content
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.
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

ComponentRoleNotes
PSTN carrierDelivers the INVITE to your SIP edge based on DID ownership.Must target the tenant-facing domain so Kamailio can capture it.
Kamailio SIP proxyAdds 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 SIPBridges 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 receiverValidates LiveKit signatures and extracts routing attributes.Skips non-SIP webhooks and continues even when routing data is missing, to keep LiveKit healthy.
Tenant webhooksConsume 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 valueParsed domain
sip:[email protected]example.com
"Display Name" <sip:[email protected]>example.com
sip:[email protected];user=phone;tag=xyzexample.com
sips:[email protected]secure.example.com
example.com:5060example.com
sip-1.example.comsip-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)

FieldMeaning
participant.name, participant.identity, participant.sidIdentifiers for the SIP caller inside the LiveKit room.
room.name, room.sidThe LiveKit room created for the call, typically prefixed with the SIP room prefix.
from_phone_numberThe caller’s number from the SIP attributes.
to_phone_numberThe called DID that triggered this room.
room_prefixConfigured SIP room prefix to distinguish SIP traffic.
sip_hostThe 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-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.