Overview

What the gateway does, and where it sits in the platform.

What it is

The Ulai SIP Gateway is a single Go binary (sip-sfu-gateway) that joins two worlds:

  • SIP/RTP — a carrier trunk, speaking SIP over UDP, TCP or TLS, carrying G.711 audio over RTP or SRTP.
  • Ulai SFU rooms — WebRTC sessions on the Ulai control plane, where browsers and AI agents meet.

A phone call that reaches the gateway becomes an ordinary participant in a room. Everything the gateway does is in service of that one sentence: decide whether the call is allowed, get a room for it, answer it, and move audio in both directions until somebody hangs up.

It handles both directions of origination:

DirectionTriggerThe gateway is
InboundA carrier sends an INVITE to the SIP listenerthe answering party (UAS)
OutboundPOST /sip/originate with a number and a trunk idthe calling party (UAC)

What it is not

  • Not an agent. The gateway never talks to a model, never decides what to say, and has no notion of a conversation. It publishes CALL_ANSWERED and lets the platform’s dispatcher summon whatever should join the room.
  • Not a media processor. No noise suppression, no VAD, no recording, no playback accounting. It transcodes and it forwards.
  • Not a rate limiter. It dials on every POST /sip/originate. If a trunk answers 429, the caller upstream is the one that has to slow down.
  • Not a routing database. Numbers, trunks, ACLs and dispatcher rules live in the platform’s routing store; the gateway only reads them.

Where it sits

   carrier / ITSP                gateway                  Ulai platform
  ┌───────────────┐        ┌──────────────────┐        ┌──────────────────┐
  │ SIP trunk     │──────▶ │ SIP listener     │        │ control plane    │
  │ (udp/tcp/tls) │ INVITE │  :5060           │──────▶ │  create / join / │
  │               │        │                  │  HTTPS │  terminate       │
  │               │◀─────  │ RTP :10000-10500 │        └──────────────────┘
  └───────────────┘  RTP   │                  │        ┌──────────────────┐
                           │ HTTP API :8082   │──────▶ │ routing store    │
                           │  /sip/originate  │  Redis │  (sip-resolver)  │
                           │  /calls /health  │        └──────────────────┘
                           │                  │        ┌──────────────────┐
                           │ WebRTC / Opus    │──────▶ │ SFU room         │
                           └──────────────────┘  SRTP  │  agent, browsers │
                                                       └──────────────────┘

Three external dependencies, all required:

  1. The control plane (ULAI_CONTROL_PLANE_URL) creates rooms, issues join tickets, and feeds the roster and lifecycle events for a session.
  2. The routing store (SIP_ROUTING_REDIS_URL) is read through ulai-sip-resolver. It is the authority on which project owns a number, which source IPs a trunk accepts, which dispatcher rule matches, and how to reach an outbound trunk. It is also where telecom events are published.
  3. The carrier, reachable at SIP_PUBLIC_IP.

Why a separate service

The SIP stack and the room gateway share no dependencies: the SIP side links no WebRTC, and the room side links no SIP. That keeps the binary — and the image — small, and it is why the only native dependency in the whole thing is libopus, because there is no production-grade pure-Go Opus encoder. Every other piece of the audio path — resampling, μ-law/A-law, RTP, SDP, SRTP — is pure Go.

Where to go next

Last modified September 21, 2026: sip module done (68f47c3)