Skip to navigation

Browser calling

Let a browser place and receive calls as an identity.
View as Markdown

Browser calling lets an agent or user talk from a web page. Your server creates a client token for an identity, such as an agent’s ID. The browser uses the token to connect over SIP over WebSocket (RFC 7118). Each call the browser makes or receives is an ordinary Session with type: "webrtc", so Rooms, recording, playback, and events work as they do for any other Session.

Flow

  1. Have the application’s webrtc_allowed_cidrs setting configured with the networks your users connect from. Browsers can connect only from these networks, and POST /v1/client_tokens returns 403 until the list is set.
  2. From your server, create a token for the identity. Send the response to the signed-in user’s browser.
  3. The browser connects with any SIP-over-WebSocket library that supports standard WebRTC media.
  4. The browser places calls, or your server dials the identity with POST /v1/sessions:dial.
  5. Your server handles the Session events and controls the call.

Create tokens only from your server. The password is returned only by the create response, so never log it or store it longer than the client needs it.

Create a token

POST /v1/client_tokens
{
"identity": "agent-1",
"ttl_seconds": 3600
}

identity is 1 to 64 lowercase letters, digits, dots, underscores, or hyphens. ttl_seconds defaults to 3600 and ranges from 60 to 31536000. The response contains everything the client needs:

{
"client_token_id": "ct_0123456789abcdef0123456789abcdef",
"identity": "agent-1",
"protocol": "sip_wss",
"ws_url": "wss://…",
"sip_uri": "sip:agent-1@…",
"username": "ct_0123456789abcdef0123456789abcdef",
"password": "…",
"ice_servers": [],
"created_at": "2026-11-05T09:00:00Z",
"expires_at": "2026-11-05T10:00:00Z"
}

Get a client token returns registered (whether a client is connected with it) and last_used_at, and never the password. List client tokens accepts an optional identity filter. Revoke a client token stops it from authenticating new connections.

Connect with JsSIP

Any compliant library works. This example uses JsSIP:

const ua = new JsSIP.UA({
sockets: [new JsSIP.WebSocketInterface(token.ws_url)],
uri: token.sip_uri,
authorization_user: token.username,
password: token.password,
register: true,
});
// Inbound calls (dialed from your server) arrive here. Answer with the
// microphone stream.
ua.on("newRTCSession", ({ originator, session }) => {
if (originator === "remote") {
session.answer({ mediaStream: micStream, pcConfig: { iceServers: token.ice_servers } });
}
});
ua.on("registered", () => {
// Any destination in your domain places the call; your server learns about
// it from the Session events.
const session = ua.call(`sip:call@${token.sip_uri.split("@")[1]}`, {
mediaStream: micStream,
pcConfig: { iceServers: token.ice_servers },
});
// Send a keypad digit as an RFC 4733 telephone event.
session.sendDTMF("5", { transportType: "RFC2833" });
});
ua.start();

Media is standard WebRTC. Audio uses Opus with in-band forward error correction. Send keypad digits as RFC 4733 telephone events, as above; SIP INFO DTMF is not supported.

The platform answers the browser’s own outbound call automatically. TURN is not provided, so ice_servers is empty and the client must be able to reach the platform over UDP.

Rules

  • One device per identity. While one token of an identity is registered or in a call, the identity’s other token cannot register or place calls. After the first device disconnects, it can take a few seconds before the other token is accepted.
  • One Session per identity. A call that arrives while the identity already has a Session is rejected as busy, whether the browser placed it or you dialed it.
  • Two live tokens per identity. Creating a third returns 409.
  • Session type. Every browser call is a Session with type: "webrtc". session.created carries the identity.
  • Unsupported from the browser. In-call SIP hold, re-INVITE, transfer, and messaging are not supported. Use the API and Rooms for call control.

Rotate a token

A token only gates new connections, so you can replace one without dropping the user. Use the second token slot:

  1. Create a new token for the same identity while the old one is still live.
  2. Switch the client to the new token’s credentials.
  3. Delete the old token.

While the old token is registered or in a call, the new token cannot register or place calls until that device disconnects. Reconnect the client with the new token after it has stopped using the old one.

Revocation, replacement, and expiry

A call already in progress on a token that was revoked, replaced, or has expired continues until it hangs up. To end it immediately, call DELETE /v1/sessions/{uuid} after you revoke the token.

Call an identity

Dial the identity from your server:

POST /v1/sessions:dial
{
"to": { "identity": "agent-1" },
"ring_timeout_sec": 30,
"max_duration_sec": 3600
}

The response returns the new Session. The call is answered when the browser answers. It returns 409 if no device is connected as that identity or the identity already has a Session. The from, caller_id_session_uuid, and trunk_id fields are not allowed when dialing an identity.

Control calls with Rooms

Control the call from your server instead of the browser. A common contact center pattern:

  • Put the customer’s Session in a Room and leave it there.
  • Move the agent between customers by removing the agent’s Session from one Room and adding it to another. The customer stays in their Room, so the customer’s audio, recording, and playback are not interrupted.
  • To put the customer on hold, play audio to the Room with Room playback, or place the customer alone in a Room.

To whisper to an agent or keep a participant from being heard, set the participant’s audience so only the intended listeners hear them. See the Room participant APIs for the full set of options.