Phone, SMS & voice
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.
- Your server mints a WebRTC session with
POST /v1/voice/calls/web. - The browser registers that session with a WebRTC client.
- 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"const sessionRes = await fetch("https://api.pozy.ai/v1/voice/calls/web", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const session = await sessionRes.json();
if (!sessionRes.ok) throw new Error(session.error);
// Send session.token and session.credentialId to the browser. Never send your API key.import os, requests
session = requests.post(
"https://api.pozy.ai/v1/voice/calls/web",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
timeout=30,
).json(){
"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.
// `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.
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. |