Guides
Browser voice test
Talk to an agent from a browser tab, with the real voice pipeline and no phone call.
A browser voice test is a real conversation with an agent through your microphone and speakers. It uses the own voice engine with everything a phone call uses: speech detection, interruption, graph routing, function tools, knowledge bases, recording and redaction. Nothing is dialled and there is no telephony charge.
In the dashboard: Agent Studio, the Talk to agent button. This page explains the two steps behind it so you can build the same thing.
Note: This needs the
ownvoice engine running, with its speech-to-text, LLM and text-to-speech backends configured by the operator, and reachable from your browser. It does not work with themockengine. For tests with no engine at all, see Testing without a phone.
Step 1: get a ticket
curl -X POST https://api.sevennadi.si/agent-voice-test/3c90c3cc-0d44-4b50-8822-8dd25736052a \
-H "Authorization: Bearer vs-0123456789abcdef0123456789abcdef"{"ws_url": "wss://voice.sevennadi.si/web", "token": "MTIzNDU2Nzg...", "expires_in": 120}ws_urlis the engine's browser endpoint, as the operator configured it. On a default local install it isws://127.0.0.1:9140/web.tokenis signed, names your account and this agent, works once, and must be used withinexpires_inseconds (120).- An unknown agent is
404Agent not found.
Step 2: open the WebSocket
const ws = new WebSocket(ticket.ws_url);
ws.binaryType = "arraybuffer";
ws.onopen = () => {
ws.send(JSON.stringify({ type: "start", token: ticket.token, sample_rate: 16000 }));
};
ws.onmessage = (event) => {
if (typeof event.data !== "string") {
playPcm16(event.data); // 16-bit mono PCM at the rate given in "ready"
return;
}
const message = JSON.parse(event.data);
if (message.type === "ready") startMicrophone(ws);
if (message.type === "clear") dropQueuedAudio(); // the caller interrupted the agent
if (message.type === "end") ws.close();
};playPcm16, startMicrophone and dropQueuedAudio are yours to write; the dashboard uses audio worklets for capture and playback.
The first message must be start. The engine redeems the token with the platform, which creates the call record and returns the agent's session. Then the engine answers:
{"type": "ready", "execution_id": "b8f0c9a2-3e5d-4c1b-9f7a-2d6e8c4b1a90", "sample_rate": 24000, "max_seconds": 300, "node": null}Audio
| Direction | Format |
|---|---|
| Browser to engine | Binary frames of 16-bit mono PCM at the sample_rate you sent in start (8,000 to 48,000 Hz; 16,000 is normal) |
| Engine to browser | Binary frames of 16-bit mono PCM at the sample_rate in ready (24,000 Hz by default), 20 ms each |
Messages from the page
| Message | Meaning |
|---|---|
{"type": "start", "token": "...", "sample_rate": 16000} | First message |
{"type": "dtmf", "digit": "5"} | A keypad press |
{"type": "stop"} | Hang up |
Messages from the engine
| Message | Meaning |
|---|---|
{"type": "ready", ...} | The call has started |
{"type": "clear"} | Drop all queued audio now |
{"type": "speaking", "text", "style"} | A sentence is about to be spoken |
{"type": "turn", "role", "text"} | A finished turn of either side, as it is stored (PII masked) |
{"type": "node", "id", "name", "path"} | Graph agents: the current node |
{"type": "tool", "name", "status"} | A tool call finished: success or error |
{"type": "error", "status", "message"} | The call could not start |
{"type": "end", "reason", "code", "execution_id"} | The call is over; the socket closes next |
Close codes
| Code | Meaning |
|---|---|
| 4401 | Bad, expired or already used token |
| 4429 | A voice test is already running for this account |
A page from an origin the engine does not allow is refused with HTTP 403 before the socket opens.
What the browser needs
- Microphone permission. Browsers grant it only on secure pages: https, or
localhostduring development. - WebSocket support, with binary frames.
- A way to capture and play raw PCM. The dashboard requires
AudioWorkletandgetUserMediaand tells the user when they are missing. - Echo handling. Capture with
echoCancellation,noiseSuppressionandautoGainControlswitched on. The engine keeps its own echo guard on too, because laptop speakers leak into the microphone. - An allowed origin. The operator lists the origins that may connect. By default only the local dashboard addresses are allowed.
- If the page is served over https,
ws_urlmust bewss://.
The call record
A test call is an ordinary execution, so it appears in Call History and in the API:
| Field | Value |
|---|---|
call_type | inbound |
telephony_data.provider | web |
agent_number | web |
context_details.web_call | true |
cost_breakdown.network | 0 |
It has a transcript, a recording, tool call logs, a graph path and the usual cost for the speech, LLM and voice components plus the platform fee. Webhooks fire as for any call. See Post-call data.
To end a test from the server side, call POST /call/{execution_id}/stop with the execution_id from ready.
Limits
| Limit | Value |
|---|---|
| Ticket lifetime | 120 seconds, single use |
| Simultaneous tests | One per account. A second ticket is issued, but its connection is refused |
| Call length | 300 seconds by default (max_seconds in ready), set by the operator |
Limitations
- A browser test is not a phone call. Audio is wide-band and the network path is different, so latency, audio quality and speech recognition can differ from what a caller on a phone line gets.
- The engine has an echo guard, not echo cancellation. It has been tested on simulated echo.
- Speech recognition is not streaming: the engine waits for the end of an utterance, so there are no partial transcripts.
- Test calls are charged for the model components and the platform fee.
Protocol details for engine developers are in Engine protocol and the endpoint is in Voice test reference.