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 browserResponse (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.