AudioBridge helper in the SDK is a
thin wrapper. Under it, every call’s audio is two live MoQT tracks that the
relay fans out. This page is the surface below the SDK. It documents the raw
publish_audio / subscribe_audio primitive, the exact namespace layout for a
call, the capability tags that route a track, and how the relay authorizes you
to touch a namespace. Use this surface when you bridge your own runtime, tape a
call into a recorder, or connect a human agent to a live caller.
Read Realtime Tracks (MoQT) first for the general
model (namespace + name + capability, connect/backoff/reconnect, track kinds).
Everything here is that model, pinned to voice.
The two tracks of a call
Thecall_sid keys every call (see
Sessions, calls, tracks & streams).
The call’s audio lives under a per-call namespace, split by direction, with
the track name audio:
The RTP media plane publishes the caller’s audio to
uplink. It subscribes to
downlink for playback. The track that you publish and the track that you
subscribe to depend on your side:
- Agent / server side (the common case) — subscribe to
uplinkto hear the caller. Publishdownlinkto talk back. - Browser-caller side — the mirror: subscribe to
downlinkto play cloud audio. Publishuplinkwith captured mic frames.
Uplink and downlink are distinct namespaces on purpose. A single track
would feed the agent its own output. The same split lets a human agent or a
supervisor subscribe to
uplink alone and hear only the caller (see
Handoffs).Publish & subscribe
publish_audio(ns, name, …) returns a publication. The publication’s
write(ts_us, frame) sends one encoded audio frame (e.g. a 20 ms Opus packet).
Objects roll into a new group about once a second.
subscribe_audio(ns, name, on_frame) delivers each object back as
(ts_us, frame). Hold both handles for the life of the call. A dropped
subscription frees the native callback. A dropped publication stops the track.
- TypeScript
- Python
string
required
The per-call track namespace,
voice/<sid>/uplink or voice/<sid>/downlink.
<sid> is the call_sid.string
required
The track name within the namespace —
audio for voice legs.string
default:""
A free-form routing intent (see Capability tags). Voice
legs use
voice/<codec>, e.g. voice/opus.number
default:"48000"
The codec sample rate of the frames that you write. Opus stays
48000.
Narrowband PCM/G.711 legs use 8000.number
default:"1"
The channel count. Voice is mono.
number
default:"20"
The frame duration per object. 20 ms is the wire cadence for Opus and G.711.
Raw objects vs. decoded frames
The RTP media plane publishes the caller’s audio onuplink as raw
8 kHz PCM16 LE objects — the runtime’s internal audio format — with no SDK
frame wrapper. To consume those bytes verbatim, use subscribe_raw. It gives
you each MoQT object’s payload untouched:
subscribe_audio when the other side is an SDK publisher (frames carry a
timestamp header). Use subscribe_raw for engine-published raw tracks. See the
audio contract for
the 8 kHz PCM16 / Opus / G.711 details.
Capability tags
A capability is an open-ended intent string attached to a track. The relay and the in-engine modules route on it: “route on intent, not on media kind.” Voice legs tag themselvesvoice/<codec>, where <codec> is one of the SDK’s
audio codecs:
So a browser caller publishes
voice/opus. A passthrough leg off a µ-law trunk
publishes voice/g711_ulaw. The capability is informational for the track
itself. Subscribers select which namespace/name to receive. The tag tells the
routing layer what the bytes are, so the routing layer can hand them to the
module that registered for that intent (an ASR node, a recorder, a
media.passthrough relay).
Namespace authorization
There is no separate ACL call. Authorization rides the connect token:1
Present your tenant token at connect
Pass your workspace API key as the token to
connect. The SDK sends the key as
a URL query parameter to relay.clutchcall.dev. The relay control plane
verifies the key before the MoQT session opens.2
The relay scopes you to your workspace's calls
A verified session can publish and subscribe to
voice/<sid> namespaces for
calls that belong to your workspace. <sid> values are unguessable per-call
keys. The relay rejects a namespace outside your tenant scope. Cross-tenant
subscription is not possible.3
Namespaces are non-empty tuples
MoQT namespaces are tuples with at least one element. When you subscribe to a
namespace that no publisher announced yet, the relay holds the subscribe.
The relay delivers it when the publisher appears. For multi-participant rooms,
the SDK scopes each participant under a
[room, identity] tuple. A single
call’s audio lives under the flat voice/<sid>/… namespace.Transport & runtimes
Tracks ride MoQT over WebTransport on the single QUIC/HTTP-3:443 plane.
Browsers negotiate the moqt-16 WebTransport subprotocol. The relay handles
connect and capped-backoff reconnect. The relay also re-establishes every
publication and subscription for you.
Two runtimes sit behind one API. The native SDK cores (Python, Go, Rust,
and the C++ FFI, clutchcall_moqt_ffi) speak QUIC. They carry a
QUIC → WebSocket fallback ladder for UDP-blocked networks. The
TypeScript SDK is a standalone WebTransport implementation and is
WebTransport-only today. There is no browser WS fallback yet. Hold the two
as separate conformance surfaces. The publish/subscribe APIs match, but the
queueing and unsubscribe lifecycle can differ.
For browser callers, you rarely call this primitive directly. The SDK’s
WebRTC-diversion capture wraps
publish_audio with encoded-Opus tapping. See
Browser audio capture
and WebTransport.Related
Realtime Tracks (MoQT)
The general publish/subscribe model that this page pins to voice.
Sessions, calls, tracks & streams
How call_sid keys the SIP dialog, the media session, and every track.
AudioBridge SDK
The high-level helper that wraps these tracks per call.
Handoffs
Subscribe to voice/<sid>/uplink to put a human on a live caller.

