Zernio
Zernio
PlatformsVoice & CallsSetupAI Voice Agents
Integrations
ElevenLabsRetell
Outbound CallsBrowser CallingCall History & Recordings
Dashboard
llms.txtOpenAPI
OverviewPlatformsAPI ReferenceResources
Voice & Calls

Browser Calling

Place a call from a web page: mint a WebRTC session on your server, register it in the browser with @telnyx/webrtc, then dial.


When you finish this page a user places a phone call from your web app with no phone. You need a voice-enabled number and a backend that holds your API key. Browser calling is a 2-step handshake: your server mints a WebRTC session, the browser registers it, then your server dials. The split exists so Zernio never bridges a call to a browser that has not finished registering.

Step 1: Mint a session on the server

Call POST /v1/voice/calls/web from your backend. The number to dial from is chosen later, at the dial step.

import Zernio from '@zernio/node';

const zernio = new Zernio();

const { data: session } = await zernio.voice.createVoiceWebSession({});
// Send session.token and session.credentialId to the browser

Response (200):

{
  "success": true,
  "token": "eyJhbGciOi...",
  "credentialId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
  "expiresAt": "2027-01-01T13:00:00Z",
  "sdk": "@telnyx/webrtc"
}

The token lives about 1 hour and must outlive the whole call, not only the handshake. Pass it to the client; never ship your API key to the browser.

Step 2: Register in the browser

Register the token with the @telnyx/webrtc SDK. When the client emits telnyx.ready, it is registered. Registration alone carries no audio: the dialed leg arrives as a telnyx.notification with a call in state ringing, and the page has to answer it.

import { TelnyxRTC } from '@telnyx/webrtc';

const client = new TelnyxRTC({ login_token: token });
let activeCall;

client.on('telnyx.ready', () => {
  // registered; tell your server it can dial
});

client.on('telnyx.notification', (notification) => {
  if (notification.type !== 'callUpdate' || !notification.call) return;
  const call = notification.call;
  if (call.state === 'ringing') {
    activeCall = call;
    call.answer();
  }
  if (call.state === 'destroy') {
    activeCall = undefined;
  }
});

client.connect();

// End the call from the page
activeCall?.hangup();

Step 3: Dial from the server

Call POST /v1/voice/calls/web/dial with to and the credentialId from Step 1. fromNumber picks which of your numbers to dial from; omit it when you own exactly one.

const { data: call } = await zernio.voice.dialVoiceWebCall({
  body: {
    credentialId: session.credentialId,
    to: '+13105551234',
    fromNumber: '+14155550100'
  }
});

console.log(call.callId);

Response (200):

{
  "success": true,
  "callId": "66e5f6a7b8c9d0e1f2a3b4c5",
  "status": "dialing",
  "direction": "outbound",
  "from": "+14155550100",
  "to": "+13105551234",
  "recordingEnabled": false
}

The answered leg is bridged to the registered browser. The call runs through the normal outbound lane: it is logged as outbound in history, honors the number's recording and transcription settings (recordOverride changes recording for this call), and terminates with call.ended or call.failed.

If it fails

A 422 on the dial step means the credentialId is unknown or expired, or no voice-enabled number matches fromNumber:

{
  "error": "Invalid or unknown WebRTC credential",
  "type": "invalid_request_error"
}

Mint a new session and register it before dialing again. A 429 means the outbound cap of 60 calls per rolling hour was hit. Every error uses the envelope in error handling.

Related

  • Outbound calls: dial without a browser.
  • Call history and recordings: read the call back.
  • Mint a browser softphone session and Dial from the browser softphone: every field.
Was this page helpful?

Outbound Calls

Place a phone call from one of your numbers with POST /v1/voice/calls, estimate its cost first, then transfer or end it while it is live.

Call History & Recordings

List every call across your numbers and both channels with GET /v1/calls, open one call, and fetch a fresh recording URL.

On this page

Step 1: Mint a session on the serverStep 2: Register in the browserStep 3: Dial from the serverIf it failsRelated