Phone, SMS & voice

Browser calling

Let your users place phone calls from a web page. Mint a short-lived session on your server, register it in the browser, then dial.

Browser calling turns a web page into a phone. Your users call from your app with a headset, using one of your numbers as caller ID, and the call shows up in history like any other outbound call. The API key never leaves your server.

#How it works

The browser registers before anything rings, so a call is never bridged to a page that is not ready.

  1. Your server mints a WebRTC session with POST /v1/voice/calls/web.
  2. The browser registers that session with a WebRTC client.
  3. Your server dials with POST /v1/voice/calls/web/dial. When the callee answers, the call is bridged to the registered browser.

#Mint a session on your server

curl -X POST https://api.pozy.ai/v1/voice/calls/web \
  -H "Authorization: Bearer $POZY_API_KEY"
Response
{
  "success": true,
  "token": "eyJhbGciOi...",
  "credentialId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
  "expiresAt": "2026-10-30T11:00:00Z",
  "sdk": "<WebRTC client package>"
}

The token lives about an hour, and it must outlive the whole call, not only the handshake. Mint a fresh session per calling session rather than caching one.

#Register in the browser

sdk names the WebRTC client package that accepts this token. Install it in your front end, log in with token, and wait for the client's ready event. Registration carries no audio on its own: the dialled leg arrives as an incoming call in the ringing state, and your page answers it.

browser.ts (shape of the code)
// `WebRtcClient` stands for the client class exported by the package named in `sdk`.
const client = new WebRtcClient({ login_token: token });

client.on("ready", () => {
  // Registered. Tell your server it can dial now.
  fetch("/api/dial", { method: "POST", body: JSON.stringify({ to: "+13125550188" }) });
});

client.on("call", (call) => {
  if (call.state === "ringing") call.answer();
});

client.connect();

Check the package's own reference for the exact event names. The flow is always the same: connect, wait for ready, answer the ringing call, hang up when done.

#Dial from your server

POST /v1/voice/calls/web/dial with to and the credentialId from the first step.

Dial
curl -X POST https://api.pozy.ai/v1/voice/calls/web/dial \
  -H "Authorization: Bearer $POZY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "credentialId": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e", "to": "+13125550188", "fromNumber": "+14155550142" }'

The response has the callId and status: "dialing".

#Dial options

Field What it does
to Number to call (required)
credentialId The session from the first step (required)
fromNumber Caller ID. Optional when you own exactly one voice-enabled number.
recordOverride Turn recording on or off for this call
ringTimeoutSeconds Default 30, up to 120

Browser calling is paused on a number while it is attached to a SIP trunk. Otherwise, browser calls follow the normal outbound rules: they appear as outbound in history, use the number's recording and transcription settings, carry branded calling when the number has it, end with call.ended or call.failed, and count toward the outbound cap of 60 calls per rolling hour.

To hang up from your server, call POST /v1/voice/calls/{id}/end. To hang up from the page, end the call in the WebRTC client.

#Errors

Status Cause Fix
422 The credentialId is unknown or expired, or no voice-enabled number matches fromNumber Mint a new session, register it, then dial
429 Outbound cap of 60 calls per rolling hour reached Wait

#Endpoints

Endpoint What it does
POST /v1/voice/calls/web Mint a browser softphone session
POST /v1/voice/calls/web/dial Dial from the browser softphone
POST /v1/voice/calls/{id}/end Hang up a live call
GET /v1/voice/calls/{id} Get a phone call

#Webhook events

Event When it fires
call.ended Call ended.
call.failed Call failed.