init, configure, flush, and server events, see the Stream message layer.
Endpoint
Transport Model
- The signaling WebSocket is used for SDP and ICE only.
- Keep the signaling WebSocket open for the lifetime of the WebRTC session. If it closes, the service closes the associated peer connection.
- The client creates the WebRTC data channel. The service accepts the incoming data channel and uses it for Stream v3 JSON messages.
- The JS client uses a data channel named
messaging; the service accepts any label. - Client microphone audio is sent as a WebRTC audio media track.
- Server translation audio is returned as a WebRTC audio media track.
- The WebRTC audio codec is Opus.
Connection Flow
The service supports one SDP offer per signaling WebSocket. To reconnect after closing or failing a peer connection, open a new WebSocket and create a new peer connection.Signaling Messages
Signaling messages are JSON text frames on the WebSocket. The signaling layer has only four message kinds:- Client to server:
offer - Server to client:
answer - Either direction:
candidate - Either direction:
end-of-candidates
answer includes the generated session_id and service version. The message shapes intentionally mirror browser WebRTC concepts: SDP descriptions contain an sdp string, and ICE candidates use the standard candidate JSON shape returned by browser APIs such as RTCIceCandidate.toJSON().
Do not send Stream v3 messages on the signaling WebSocket. Send them on the WebRTC data channel after it opens.
Trickle ICE
Use trickle ICE. Do not wait for ICE gathering to complete before sending the offer. On the client:- Send the SDP offer as soon as
createOffer()andsetLocalDescription()complete. - Send each
icecandidateevent to the service as acandidatesignal. - When the browser emits an
icecandidateevent with a null candidate, sendend-of-candidates. - Apply each server
candidatewithaddIceCandidate(). - Treat server
end-of-candidatesas the remote end-of-candidates marker.
- Remote ICE candidates received before the service has accepted the offer are queued and applied after the offer is processed.
- Local ICE candidates gathered by the service before the answer is sent are queued and flushed immediately after the answer.
- When service-side ICE gathering completes, the service sends
end-of-candidates.
Peer Connection Setup
The JS client creates the peer connection withbundlePolicy: "max-bundle", creates a data channel named messaging, and adds the caller-provided audio track before creating the offer:
ICE Servers And Networking
The service gathers IPv4 UDP candidates and is configured with public STUN servers. In deployed environments it can also use Metered TURN whenTURN_USERNAME and TURN_PASSWORD are configured.
The JS client currently uses the browser’s default ICE configuration and relies on the service-side ICE configuration. Custom clients may provide their own iceServers configuration. Include TURN for production clients that need to work on mobile networks, corporate networks, VPNs, or any environment where direct UDP may be blocked. Without TURN, peer connections can fail on relay-only networks.
The service uses a bounded UDP port range. Locally this defaults to 10000-60000; deployed environments may choose a narrower range. Make sure the selected UDP range is reachable from clients or from the configured TURN relay.
Receiving Server Media
The service adds its outbound translation audio track before answering. Listen for the browsertrack event and attach the received stream to an audio output:
Minimal Browser Signaling Skeleton
This intentionally omits the Stream v3 message contents. After the data channel opens, follow the Stream message layer for the JSON messages to send and receive.Operational Notes
- Non-text WebSocket frames are ignored, except close frames, which end the session.
- If the peer connection enters
disconnectedorfailed, the service allows a reconnect grace period before closing the session. - Idle sessions are closed after the configured inactivity timeout.
- Renegotiation is not supported on an existing signaling WebSocket. Start a new session instead.