The Phonely Web SDK lets you add browser-based voice calls to your app without exposing your Phonely API key to the browser.
Your server creates a short-lived web call session with Phonely, then your browser passes that session bundle to the SDK.
Install
npm install @phonely-ai/web
Use @phonely-ai/web v0.0.8 or newer.
How It Works
- Your browser asks your backend to start a web call for an agent.
- Your backend calls Phonely with your API key.
- Phonely returns a short-lived session bundle.
- Your backend returns that bundle to your browser.
- The Web SDK uses the bundle to join the call and read the live transcript.
Never send your Phonely API key to the browser. Keep it in your backend only.
Create A Web Call Session
Call this endpoint from your backend.
POST https://db.phonely.ai/api/agents/{agent_id}/web-call-session
X-Authorization: YOUR_API_KEY
Content-Type: application/json
Request Body
The body is optional. Send {} if you do not need metadata.
{
"metadata": {
"callOverrides": {}
}
}
Response
Return this object from your backend to your browser.
{
"callId": "abc123",
"webCallUrl": "https://...",
"meetingToken": "short-lived-meeting-token",
"phonelyToken": "short-lived-transcript-token",
"expiresAt": "2026-06-12T03:00:00+00:00",
"transcriptUrl": "https://db.phonely.ai/api/calls/abc123/transcript"
}
| Field | Description |
|---|
callId | The Phonely call ID for this session. |
webCallUrl | The URL the SDK uses to join the web call. |
meetingToken | A short-lived token for this web call session. |
phonelyToken | A short-lived token the SDK uses for this call’s transcript. |
expiresAt | When the short-lived tokens expire. |
transcriptUrl | The transcript endpoint for this call. Return this value to the SDK as received. |
Backend Example
export async function POST(request: Request) {
const { agentId, metadata } = await request.json();
const response = await fetch(
`https://db.phonely.ai/api/agents/${encodeURIComponent(agentId)}/web-call-session`,
{
method: "POST",
headers: {
"X-Authorization": process.env.PHONELY_API_KEY!,
"Content-Type": "application/json",
},
body: JSON.stringify(metadata ? { metadata } : {}),
}
);
if (!response.ok) {
return Response.json(await response.json(), { status: response.status });
}
return Response.json(await response.json());
}
Browser Example
import Phonely from "@phonely-ai/web";
const phonely = new Phonely({
createWebCallStart: async (options) => {
const response = await fetch("/api/web-call/start", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(options),
});
if (!response.ok) {
throw new Error("Failed to start web call");
}
return response.json();
},
transcript: {
enabled: true,
autoStart: true,
},
});
const callId = await phonely.call({ agentId: "agent_123" });
Live Transcript
When transcript.autoStart is enabled, the SDK reads the transcript using the transcriptUrl and phonelyToken from the session response. Return the session response from your backend unchanged so the SDK can use those fields.
Listen for transcript updates:
phonely.on("transcript-message", ({ allMessages }) => {
console.log(allMessages);
});
Token Expiration
Session tokens are short lived. If a token expires, start a new web call session from your backend.