Browser widget
The browser widget lets a visitor talk to your bot from a web page, with no phone call. The page connects to the gateway over a WebSocket, sends microphone audio up and plays the bot's spoken reply. Browser sessions use the same speech recognition, text to speech and AI brain settings as phone calls, so the bot answers the same way on the web. See AI brain for the brain settings. The language comes from the language value in the page.
If your gateway is run for you, go to Hosted customers.
Enable the endpoint
The browser endpoint is off by default. Add these lines to .env and restart the gateway. If you have not installed the gateway yet, start with Quick start: binary or Quick start: Docker.
WEBSOCKET_AUDIO_ENABLED=true
WEBSOCKET_AUDIO_HOST=127.0.0.1
WEBSOCKET_AUDIO_PORT=8082
| Variable | Default | Meaning |
|---|---|---|
WEBSOCKET_AUDIO_ENABLED |
false |
true starts the browser endpoint. |
WEBSOCKET_AUDIO_HOST |
0.0.0.0 |
Address to listen on. Use 127.0.0.1 when a proxy on the same host fronts the endpoint. |
WEBSOCKET_AUDIO_PORT |
8082 |
Port to listen on. |
Every browser session takes one slot in the same concurrent-call limit as phone calls, which is your licence cap. An open endpoint lets anyone spend your speech and AI provider budget and can crowd out real phone calls. Do these three things before you give out the address:
- Put a TLS proxy such as nginx in front, so pages connect with
wss://. Browsers also require HTTPS before they allow the microphone. - Bind the endpoint to
127.0.0.1and keep port 8082 closed in the firewall. - Set an origin allowlist, as described in Secure it.
A minimal nginx location that passes the WebSocket upgrade to the gateway:
location / {
proxy_pass http://127.0.0.1:8082;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 3600s;
}
The location path is your choice. If you use a path other than /, the page connects to wss://your-domain/that-path.
Secure it
The origin allowlist is the main control. The API key is a lightweight extra gate, because it sits in the page where any visitor can read it.
WEBSOCKET_AUDIO_ALLOWED_ORIGINS=https://www.example.com,*.example.org
WEBSOCKET_AUDIO_API_KEY=choose-a-long-random-string
WEBSOCKET_AUDIO_MAX_PER_IP=5
WEBSOCKET_AUDIO_ALLOWED_BOT_IDS=support,sales
| Variable | Default | Meaning |
|---|---|---|
WEBSOCKET_AUDIO_ALLOWED_ORIGINS |
empty | Comma-separated allowed origins. Empty allows any origin. A page from another origin is refused with HTTP 403. |
WEBSOCKET_AUDIO_API_KEY |
unset | When set, the page must send it as ?apiKey= on the address or as an x-api-key header. Unset means no authentication. |
WEBSOCKET_AUDIO_MAX_PER_IP |
5 |
Simultaneous connections allowed from one client address. |
WEBSOCKET_AUDIO_MAX_PAYLOAD_BYTES |
1048576 |
Largest single message accepted, in bytes. |
WEBSOCKET_AUDIO_IDLE_TIMEOUT_MS |
300000 |
Close a session with no audio or control messages for this long (pings do not count). 0 disables. |
WEBSOCKET_AUDIO_MAX_SESSION_MS |
3600000 |
Hard limit on one session's length. 0 disables. |
WEBSOCKET_AUDIO_GREETING_MAX_CHARS |
500 |
Longest greeting text the page may send; longer text is cut. |
WEBSOCKET_AUDIO_ALLOWED_BOT_IDS |
empty | Comma-separated bot ids a page may choose. Empty allows any. |
HTTP_TRUST_PROXY |
false |
Read the client address from X-Forwarded-For when the proxy is not on the same host. |
An origin entry is either an exact origin such as https://www.example.com, or a host wildcard such as *.example.org. Wildcards match on the host only, so https://*.example.org is not treated as a wildcard. Write the bare host form, *.example.org.
The per-address limit needs the real visitor address. Behind a proxy on the same host the gateway reads it from X-Forwarded-For, X-Real-IP or CF-Connecting-IP automatically. Behind a proxy on another machine, set HTTP_TRUST_PROXY=true.
What a page may send
When a page starts a session it can send a metadata object. The gateway accepts these:
language, the speech language code.botId, the bot to talk to. If it is not inWEBSOCKET_AUDIO_ALLOWED_BOT_IDSthe gateway logs a warning, ignores it and uses the default bot.- Any other keys you choose, such as a page name or plan. They are passed to the brain as context.
The gateway always removes these keys, because it owns them: userId, user_id, sessionId, session_id, uuid, apiKey, api_key, authorization, __proto__, constructor and prototype. Metadata that is not an object or is larger than 8 KB is rejected. A page cannot choose the TTS provider, model or voice; those come from your .env.
The address may also carry userId, a value you choose to link one visitor's conversations together. Make it unguessable, such as a random UUID. Never use an email address or a sequential number.
Close codes
| Code | Meaning |
|---|---|
| 4001 | Wrong or missing API key. |
| 4003 | Call limit reached. Phone calls share this limit. |
| 4008 | Idle timeout. |
| 4009 | Maximum session length reached. |
The SDK does not reconnect on any of these. Your page decides whether to offer a new session.
Add it to a page
The gateway's HTTP port serves the browser SDK as aivg-sdk.js, for example http://your-host:8081/aivg-sdk.js. The Docker image serves it on the HTTP port; binary installs and hosted customers should use the npm package or the copy Vexyl provides. It is also published as the npm package @vexyl.ai/aivg-sdk, which exports AIVoiceGateway. Copy aivg-sdk.js to your own site and load it with a script tag.
<button id="start">Talk</button>
<script src="aivg-sdk.js"></script>
<script>
const voice = new AIVoiceGateway({
serverUrl: 'wss://your-domain',
language: 'en-IN',
autoGreet: true,
greetingMessage: 'hi',
onConnect: ({ uuid }) => console.log('Connected:', uuid),
onTranscript: (text, { isFinal }) => console.log('User:', text),
onResponse: (text) => console.log('AI:', text),
onStatus: (status) => console.log('Status:', status)
});
document.getElementById('start').onclick = async () => {
await voice.connect();
await voice.startListening();
};
</script>
The snippet calls connect() and startListening() from a button click. Browsers allow the microphone and audio playback only after a user action.
With autoGreet: true the SDK sends greetingMessage (default hi) to the brain once after connecting, so the bot speaks first and the visitor knows to talk. The gateway accepts one greeting per connection and cuts it to WEBSOCKET_AUDIO_GREETING_MAX_CHARS.
The Docker image also serves test pages on its HTTP port (the binary bundle does not include them): /demo.html (full controls), /index.html (waveform) and /3d.html (animated sphere). Open /demo.html first to check the endpoint before you write your own page.
SDK reference
| Item | Meaning |
|---|---|
isConnected |
True while the session is open. |
isListening |
True while the microphone is streaming. |
isPlaying |
True while reply audio is playing. |
isSpeakingSession |
True while the bot is mid-turn. |
connect() |
Open the session. |
disconnect() |
Close the session. |
startListening() |
Start the microphone. |
stopListening() |
Stop the microphone. |
setLanguage(code) |
Change the speech language. |
getFrequencyData() |
Audio levels, for drawing a waveform. |
Microphone audio goes up as WebM/Opus. With Google Chirp3-HD voices the reply streams sentence by sentence as it is made, controlled by WS_AUDIO_TTS_STREAMING (default true). Other voices send one audio message per sentence.
Messages from the gateway
The SDK handles these for you. Use them directly only if you write your own client.
| Message | Meaning |
|---|---|
connected |
Session open; carries the session uuid. |
status |
State change: listening, processing, speaking or ready. |
audio |
Binary reply audio frames, played by the SDK and followed by audio_complete. |
transcript |
What the visitor said, with isFinal set when the phrase is complete. |
response_delta |
A streamed fragment of the reply text. |
response |
The full reply text, once per turn. |
audio_complete |
The reply audio for this turn has been sent. |
error |
A problem, with a code and message. |
hangup |
The bot ended the conversation; carries a reason. |
transfer |
The bot asked for a human (reason llm_requested). |
pong |
Answer to a client ping. |
closed |
The session is ending, with code IDLE_TIMEOUT or MAX_SESSION. |
Reply audio arrives as binary messages between these. A browser session has no phone leg, so on transfer the session stays open and your page decides what to do, for example show a contact form or a phone number.
The page can send start, stop, mute, unmute, config, greeting, audio_playback_complete and ping. The SDK sends these for you.
Hosted customers
If Vexyl runs your gateway, you do not set the variables above. We give you a wss:// address and an API key, and we allow the origins you list. See Hosted service, then use the page code in the previous section with that address.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
A browser or curl request to the port returns 426 Upgrade Required |
Normal. It shows the endpoint is up. Connect with a WebSocket client or the SDK. |
| The proxy answers 400 or 502 | The proxy did not forward the Upgrade headers. Check the Upgrade and Connection headers and proxy_http_version 1.1 in the nginx block above. |
| HTTP 403 at connect, and the log shows an invalid origin | Add the page's exact origin to WEBSOCKET_AUDIO_ALLOWED_ORIGINS. |
| Close code 4001 | The page sends no key or a wrong one. Match WEBSOCKET_AUDIO_API_KEY. |
| Close code 4003, or "too many connections" in the log | Phone and browser sessions share one call limit. Free slots. A page that opens connections and never closes them also causes this. |
| HTTP 429 when the connection opens | One client address hit the per-IP limit at the handshake (no close code is sent). Raise WEBSOCKET_AUDIO_MAX_PER_IP if one address legitimately needs more. |
| Close code 4008 or 4009 | The visitor was silent past the idle limit, or the session reached its length limit. Raise WEBSOCKET_AUDIO_IDLE_TIMEOUT_MS or WEBSOCKET_AUDIO_MAX_SESSION_MS, or have the page start a new session. |
| The bot is heard but cannot hear you | The page must be on HTTPS and the visitor must allow the microphone. |
| The bot answers but there is no sound | Browsers block audio until a user action. Start the session from a click. |
The bot ignores botId |
The id is not in WEBSOCKET_AUDIO_ALLOWED_BOT_IDS; the default bot is used. |
For problems with the brain's replies, see AI brain. For other issues, see Troubleshooting. The HTTP endpoints are in HTTP API, and what the bot does mid-conversation is in Conversation behaviour. Webhooks and billing records for browser sessions are in Webhooks and billing.