Browser Calls with WebRTC
Let your users talk to your AI voice agent directly from a web page — no phone call needed. This guide walks through the required scripts, HTML elements, connection lifecycle, and events.
Overview
Voice Elements provides a lightweight JavaScript library — InventiveWebRTC.js — that handles the entire WebRTC handshake, ICE negotiation, and media pipeline for you. Your page only needs to call a single function to connect and listen for a handful of DOM events to track call state.
The connection flow is simple: the library opens the user's microphone, establishes a WebSocket to the Voice Elements signaling server, negotiates an SDP offer/answer, and streams two-way audio between the browser and your AI voice agent.
https:// (or localhost during development).
Prerequisites
- ✓ A Voice Nexus Service account with at least one configured voice agent.
- ✓ The WebSocket URL (WSS) for your Voice Elements WebRTC server. This is provided by Voice Elements when your account is provisioned.
- ✓ Your page served over HTTPS so the browser will grant microphone permission.
- ✓ A modern browser — Chrome, Edge, Firefox, or Safari.
Required Scripts
Two JavaScript files must be included on every page that uses browser calling. Load them in this order, after the required HTML elements (see next section).
| Script | Purpose |
|---|---|
| adapter.js |
A WebRTC compatibility shim (from the webrtc-adapter project).
Normalizes browser differences in the getUserMedia and RTCPeerConnection APIs.
Must be loaded before InventiveWebRTC.js.
|
| InventiveWebRTC.js | The Voice Elements WebRTC client library. Manages the WebSocket signaling channel, SDP negotiation, ICE candidate exchange, microphone access, and media streaming. Exposes the public API described in this guide. |
<!-- Load after the required HTML elements -->
<script src="adapter.js"></script>
<script src="InventiveWebRTC.js"></script>
adapter.js must come first. InventiveWebRTC.js references DOM elements by ID at parse time, so the HTML elements it depends on must already exist in the document.
Required HTML Elements
InventiveWebRTC.js looks up several elements by id when it first loads.
Place these elements in your page before the script tags.
<!-- Local mic preview (muted so the user doesn't hear themselves) -->
<audio id="sourceAudio" autoplay muted></audio>
<!-- Remote audio from the AI agent -->
<audio id="remoteAudio" autoplay></audio>
<!-- Optional status label the library updates -->
<span id="lblStatus">Disconnected</span>
| Element ID | Type | Purpose |
|---|---|---|
| sourceAudio | <audio> |
Receives the local microphone stream. Should be muted to prevent echo. |
| remoteAudio | <audio> |
Plays back audio from the remote AI agent. Should have autoplay. |
| lblStatus | any element | Optional. The library may update its textContent with status messages during SDP negotiation. |
Connecting a Call
To start a browser call, invoke one of two functions. Both will prompt the user for microphone access (if not already granted), open a WebSocket to the signaling server, and negotiate the WebRTC peer connection automatically.
Option A — Connect with a WebSocket URL
Use this when you already know the WSS endpoint for your Voice Elements server.
IVLConnect("wss://your-server.voiceelements.com:61053/");
Option B — Connect with an Application ID
If your account uses Application ID-based routing, call IVLConnectApplicationID instead. The library will look up the correct WebSocket URL automatically via the Voice Elements REST API.
IVLConnectApplicationID("your-application-id");
Passing context to your agent
When the WebSocket opens, the library sends a handshake that includes the current page URL. You can append custom data (such as a phone number or session token) by setting the global variable ivlAppendToUrlOnOpen before calling IVLConnect.
// The handshake URL will be sent as:
// "https://yoursite.com/call" + "/" + "7205551234"
ivlAppendToUrlOnOpen = "7205551234";
IVLConnect("wss://your-server.voiceelements.com:61053/");
Your VNS CallStart webhook receives this URL, so you can parse the appended value to determine which agent configuration to return.
Disconnecting
To hang up, call:
IVLDisconnect();
This closes the WebSocket, removes the local media stream from the peer connection, and tears down the RTCPeerConnection.
A socketStatusEvent with status IVLSocketState.Disconnected fires once the socket closes.
Events
The library dispatches CustomEvents on document so your page can react to state changes without polling. Register listeners before calling IVLConnect.
| Event Name | e.detail Properties |
Description |
|---|---|---|
| socketStatusEvent | status, time |
Fired whenever the connection state changes. status is one of the IVLSocketState enum values (see table below). |
| mediaStatusEvent | status, time |
Fired when remote audio begins or ends. status is IVLMediaState.Connected or IVLMediaState.Disconnected. |
| messageEvent | message, time |
Diagnostic log messages from the library. Useful for debugging. Only fires when ivlLogs = true. |
| applicationMessageEvent | message |
Fired when the server sends a custom application message. The message property is a JSON string you can parse to handle app-specific commands. |
| authenticationEvent | modulus, publicExponent |
Fired if the server requests authentication. Respond by calling IVLAuthenticate(username, password). |
IVLSocketState Values
| Constant | Value | Meaning |
|---|---|---|
| IVLSocketState.Disconnected | 0 | No active connection. |
| IVLSocketState.Permissions | 1 | Requesting microphone permission from the user. |
| IVLSocketState.Connecting | 2 | WebSocket opened; SDP/ICE negotiation in progress. |
| IVLSocketState.Connected | 3 | Call is live — two-way audio is flowing. |
Example: Listening for connection state
document.addEventListener("socketStatusEvent", function (e) {
switch (e.detail.status) {
case IVLSocketState.Disconnected:
console.log("Call ended");
break;
case IVLSocketState.Permissions:
console.log("Requesting microphone...");
break;
case IVLSocketState.Connecting:
console.log("Connecting...");
break;
case IVLSocketState.Connected:
console.log("Connected — call is live!");
break;
}
});
Sending & Receiving Application Messages
You can exchange custom JSON payloads between your page and the Voice Elements server while a call is in progress. This is useful for sending UI commands (e.g. a "Goodbye" button) or receiving data from the agent.
Sending a message to the server
// Send an arbitrary JSON object to the server-side application
IVLSocketSendApplicationMessage({
__type: "MyApp.EndCall",
version: 1,
reason: "user-clicked-hangup"
});
Receiving a message from the server
document.addEventListener("applicationMessageEvent", function (e) {
var msg = JSON.parse(e.detail.message);
console.log("Received from server:", msg);
// Handle application-specific message types here
});
Sending DTMF Tones
If your agent expects DTMF input (e.g. "press 1 for billing"), you can send tones programmatically from JavaScript. DTMF sending is set up automatically during the WebRTC negotiation.
// Send a single digit
sendTone("1");
// Send multiple digits
sendTone("1234#");
Each tone is sent with a 250 ms duration and 50 ms gap by default.
Full Example
Below is a minimal, self-contained HTML page that places a "Call Agent" button on screen. Clicking it connects to your AI voice agent via WebRTC. Clicking it again hangs up.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Browser Call Demo</title>
</head>
<body>
<h1>Talk to Our AI Agent</h1>
<p>Status: <span id="lblStatus">Disconnected</span></p>
<button id="callBtn">Call Agent</button>
<!-- Required audio elements -->
<audio id="sourceAudio" autoplay muted></audio>
<audio id="remoteAudio" autoplay></audio>
<!-- Scripts (order matters) -->
<script src="adapter.js"></script>
<script src="InventiveWebRTC.js"></script>
<script>
var WSS_URL = "wss://your-server.voiceelements.com:61053/";
var callBtn = document.getElementById("callBtn");
var isActive = false;
// Listen for state changes
document.addEventListener("socketStatusEvent", function (e) {
var lbl = document.getElementById("lblStatus");
switch (e.detail.status) {
case IVLSocketState.Permissions:
lbl.textContent = "Requesting mic…";
break;
case IVLSocketState.Connecting:
lbl.textContent = "Connecting…";
break;
case IVLSocketState.Connected:
lbl.textContent = "Connected";
callBtn.textContent = "Hang Up";
isActive = true;
break;
case IVLSocketState.Disconnected:
lbl.textContent = "Disconnected";
callBtn.textContent = "Call Agent";
isActive = false;
break;
}
});
callBtn.addEventListener("click", function () {
if (!isActive) {
IVLConnect(WSS_URL);
} else {
IVLDisconnect();
}
});
</script>
</body>
</html>
WSS_URL with the actual WSS endpoint provided by Voice Elements for your account.
Troubleshooting
Microphone permission denied
The browser blocked microphone access. Ensure the page is served over HTTPS and the user clicks "Allow" on the permission prompt. Check that no browser extension is blocking media access.
Call connects but no audio
Verify that both <audio> elements exist with the correct IDs. Ensure remoteAudio has the autoplay attribute. Some browsers require a user gesture before audio can play — make sure the call is initiated by a button click.
WebSocket connection fails
Confirm the WSS URL is correct and that port 61053 (or your configured port) is not blocked by a firewall. Mixed-content rules will block ws:// connections from an HTTPS page — always use wss:// in production.
Enabling debug logs
Set ivlLogs = true; before calling IVLConnect. This activates the messageEvent custom event. Listen for it to capture detailed log output from the library.
API Quick Reference
| Function / Variable | Description |
|---|---|
| IVLConnect(wssUrl) | Open mic, connect WebSocket, start WebRTC call. |
| IVLConnectApplicationID(appId) | Look up WSS URL by Application ID, then connect. |
| IVLDisconnect() | Hang up — close socket & peer connection. |
| IVLAuthenticate(user, pass) | Respond to an authentication challenge. |
| IVLSocketSendApplicationMessage(obj) | Send a custom JSON message to the server. |
| sendTone(digits) | Send DTMF tones over the active call. |
| ivlAppendToUrlOnOpen | Set before IVLConnect to append data to the handshake URL. |
| ivlLogs | Set to true to enable messageEvent diagnostic logging. |