Skip to main content
Beta: Realtime conversations over WebRTC are in beta. Connect a browser or other WebRTC client directly to a Telnyx AI Assistant. Media travels as WebRTC tracks: the caller’s microphone (and optionally camera) to Telnyx, the assistant’s voice (and optionally its avatar video) back. Conversation events travel as JSON on a WebRTC data channel named oai-events, or on a WebSocket when the call is set up over one. The assistant’s behavior — instructions, model, voice, tools, transcription and interruption settings — is configured on the assistant itself. The WebRTC connection carries media and events only. Use WebRTC for browser and mobile clients. For a server-side integration that streams PCM audio, use the conversation WebSocket instead.

How a call is set up

Set up a call in one of two ways. Media, avatars, the camera and the conversation events are the same in both. With POST /calls:
  1. The browser creates an RTCPeerConnection that relays through Telnyx TURN, adds the microphone track, the oai-events data channel and, for an avatar or camera, a video transceiver. It waits for ICE gathering to complete.
  2. The browser sends the SDP offer to the application server.
  3. The application server forwards the offer to POST /v2/ai/assistants/{assistant_id}/calls with its Telnyx API key. The API key never reaches the browser.
  4. Telnyx starts a conversation with the assistant and returns the SDP answer.
  5. The application server returns the answer to the browser, which applies it.
  6. Media and the data channel connect. The conversation, including the assistant’s greeting, starts once the oai-events data channel opens.

Migrate an existing avatar client

Replace the avatar WebSocket connection and its signaling frames with the call setup above. Do not send avatar.session.ready, avatar.webrtc.offer, avatar.ice_candidate, avatar.webrtc.answer, or avatar.media.ready over the data channel. Mid-call renegotiation uses the separate webrtc.offer and webrtc.answer events described below. The greeting starts when the data channel opens, rather than waiting for the previous avatar.media.ready playback acknowledgement. Handle rejected media-element play() promises and provide a user action to start playback. An open data channel does not establish that the browser is playing the media; autoplay restrictions can prevent the user from hearing the beginning of the greeting.

Configure ICE servers

Telnyx relays the call’s media only through Telnyx TURN, and Telnyx TURN relays only between Telnyx addresses. Configure the client to relay through Telnyx TURN with iceTransportPolicy: "relay". Direct connections and third-party TURN servers do not connect.
Telnyx TURN requires credentials. See STUN/TURN servers for the endpoints and how to obtain credentials. These credentials authenticate the browser to the relay; they are separate from the Telnyx API key used by the application server. The configuration above requires access to turn.telnyx.com on UDP or TCP port 3478. TCP on port 3478 is not an HTTPS/443 fallback. If a firewall or VPN blocks both transports, the call cannot connect even when HTTPS requests succeed. Handle ICE gathering and connection failures in the client, and release local media tracks when setup fails.

Create the offer

Add the tracks and the data channel before creating the offer, then wait for ICE gathering to complete: Telnyx does not accept trickled candidates, so the offer must carry them.
The offer must include: All m-lines must share one transport (a=group:BUNDLE), as browsers do by default.

Create the call

The application server forwards the offer with its Telnyx API key:
Optional query parameter: Send the offer as a multipart/form-data body with an sdp field, or as a raw application/sdp body. The multipart form also accepts an optional session field to pass dynamic variables for this conversation:
A successful request returns 201 Created with the SDP answer as an application/sdp body. Return the answer to the browser unchanged. Every request to the relay starts a call billed to the Telnyx account. Authenticate and authorize the browser’s user before forwarding the offer, and rate-limit calls per user.
Client-supplied dynamic variables override same-named assistant defaults or values returned by the dynamic-variable webhook. Unspecified variables keep those values.

Errors

Errors return a JSON error object with error.code:

Set up the call over a WebSocket

Instead of POST /calls, a client can open the conversation WebSocket with transport=webrtc, receive the ICE servers to use, and send its offer on the socket. Media still travels over WebRTC; every conversation event travels on the WebSocket, and the offer needs no data channel.

Connect

Authenticate the handshake as for the conversation WebSocket, with an Authorization header. Browsers cannot set headers on a WebSocket; they pass the credential as WebSocket subprotocols instead: the telnyx-auth-ws marker, then base64url.bearer.telnyx. followed by the credential in base64url without padding. Send either the header or the subprotocol credential, not both.
The credential authenticates as the Telnyx account. Do not ship an API key in a public web page: for untrusted users, connect the browser through the application server, which adds the Authorization header after authenticating and rate-limiting the user as for POST /calls. A refused connection fails the handshake with the HTTP status and error code of POST /calls, for example 401 unauthorized or 404 assistant_not_found.

Receive the ICE servers

Telnyx speaks first, with the ICE servers and transport policy for the call:
Create the RTCPeerConnection with them, then add the tracks as in Create the offer, without the oai-events data channel, and wait for ICE gathering to complete:

Send the offer

session is optional and takes the same dynamic variables as POST /calls. Telnyx replies with the SDP answer and the call’s id:
Apply the answer. The conversation, including the assistant’s greeting, starts once the media connects, and session.created arrives on the WebSocket. Events sent before then are held and delivered in order once the conversation starts.

Signaling errors

A refused offer returns an error event; the WebSocket stays open and the client may send another offer.

Events, renegotiation and ending the call

  • The WebSocket carries the same events as the data channel, with the same differences from the conversation WebSocket: microphone audio and the assistant’s voice travel on the media tracks. Each message can be up to 1 MiB; the data channel’s SCTP limit does not apply.
  • Add video during a call by sending webrtc.offer on the WebSocket; Telnyx replies with webrtc.answer there. The offer does not need a data channel.
  • Close the WebSocket to end the call. When the conversation ends, Telnyx closes the WebSocket: with code 1000 normally, or 1008 or 1011 when the session failed, after an error event. Close the peer connection and stop the local tracks as in End the call.
  • The media must connect within 20 seconds of the offer; otherwise Telnyx ends the call.

Exchange events on the data channel

The oai-events data channel carries the same JSON events as the conversation WebSocket, with these differences: Every other event works as on the WebSocket, including speech detection and transcripts, text turns, interruption and barge-in, response.cancel, tool calls and vision frames. Keep each serialized message below 1 MiB and within the connection’s negotiated pc.sctp.maxMessageSize. The SCTP limit can be smaller than the Telnyx event limit. Measure the UTF-8 bytes of the entire JSON string, including any base64 image and metadata, before calling send(). The assistant’s voice must stream PCM at 8, 12, 16, 24 or 48 kHz. Otherwise the call starts with an error event on the data channel:

Show an avatar

Beta: AI Assistant avatars are in beta. See the Avatars guide for configuration, voice compatibility and pricing. When the assistant has saved avatar_settings and the offer includes a video m-line that can receive VP8 (recvonly or sendrecv), the avatar’s video arrives on that video track and its voice on the audio track, already synchronized. Play both. An offer without a receivable video m-line starts a voice-only call, even when the assistant has an avatar. To keep a receivable video m-line, for example to send the camera, without the avatar, create the call with ?avatar=false; a video m-line added during the call then does not start the avatar either. The data channel reports the avatar’s state with avatar.updated:
A runtime avatar failure also sends an avatar.error event with error.fatal: false. The conversation continues; the voice switches back on the same audio track automatically.

Avatar errors

Send the camera

To let the assistant see the caller, send the camera on the video m-line instead of recvonly. Add the camera track before creating the offer, in place of the recvonly transceiver:
The m-line is then sendrecv: the camera goes up and the avatar, if any, comes down on the same m-line. Telnyx samples the camera every 2 seconds into a JPEG of at most 1280 pixels wide, tagged camera, and buffers it for the vision tool. The assistant needs the vision tool and a vision-capable model; buffered frames do not trigger responses on their own. Image frames sent on the data channel are buffered the same way. For camera or screen snapshots sent as image events, reduce JPEG quality or image dimensions until the complete message fits both data-channel limits. Reducing quality or dimensions can make small text and fine detail harder to interpret. These message-size limits apply to image events, not to the native camera RTP track described above. Send only while the data channel is open, monitor bufferedAmount to avoid accumulating stale snapshots, and handle send() exceptions. If a frame cannot fit or be sent, surface the failure in the application’s camera-sharing UI; reducing the image and retrying are client responsibilities. A successful send() queues the message locally and does not confirm that the assistant has used the image.

Add video during a call

A call that started without a video m-line can add one. Add the transceiver, set the new offer as the local description, wait for ICE gathering, and send it on the data channel as a webrtc.offer event. Telnyx replies with a webrtc.answer event; apply it with setRemoteDescription:
If the assistant has an avatar, it starts once the new video m-line can receive it. Send one offer at a time and keep a single video m-line. A refused offer returns an error event instead:

End the call

Close the peer connection to end the call. Telnyx ends the conversation as soon as the oai-events data channel closes. When a client disappears without closing, for example after a network loss, Telnyx ends the conversation within seconds once media stops arriving.
The data channel must open within 20 seconds of the call request; otherwise Telnyx ends the call.