Skip to main content

Before you start

Headless Mode is available on our Pro tiers only. If your account is on a lower tier, upgrade your plan from the dashboard before following this guide.
You need your widget’s embed snippet. In the dashboard, open your campaign and go to the Web Widget tab → Embed Code panel. The snippet there has your real widget ID and host pre-filled - the only thing you add is the hide-launcher attribute.
1

Embed the widget with hide-launcher

Paste the embed snippet into your page and add the hide-launcher attribute. The widget mounts and runs, but renders no UI of any kind.
<voiceai-widget id="YOUR_WIDGET_ID" host="your-domain.com" hide-launcher></voiceai-widget>
<script defer src="https://your-domain.com/widget/voiceai-widget.umd.js"></script>
2

Make any element the trigger

Any button, link, or image on your page can start a call or send a chat message.
<button id="call-btn" disabled>Loading…</button>

<script>
  const widget = document.querySelector('voiceai-widget');
  const btn = document.getElementById('call-btn');

  btn.onclick = () => widget.startCall();
</script>
You don’t need to wait for the widget to load before calling startCall() - calls made early are buffered and fire automatically once the widget is ready.
3

React to events

The widget reports everything through standard DOM events on the <voiceai-widget> element. Use them to drive your UI state.
widget.addEventListener('widget.ready', () => {
  btn.disabled = false;
  btn.textContent = 'Talk to our AI';
});
widget.addEventListener('voice.started', () => {
  btn.textContent = 'End call';
  btn.onclick = () => widget.stopCall();
});
widget.addEventListener('voice.ended', () => {
  btn.textContent = 'Talk to our AI';
  btn.onclick = () => widget.startCall();
});
4

Handle errors

In Headless Mode your UI is the only UI - there is no widget surface to display problems. At minimum, handle these:
widget.addEventListener('voice.mic-permission-denied', () => {
  // Tell the visitor to allow the microphone via the address-bar lock icon
});
widget.addEventListener('voice.error', (e) => console.error(e.detail.message));
widget.addEventListener('chat.error', (e) => console.error(e.detail.message));
widget.addEventListener('widget.error', (e) => console.error(e.detail.message));
Serve your page over https:// (or http://localhost during development). Never test from a file:// page - browsers don’t persist microphone permission there, which causes repeated permission prompts and calls that die with no audio. See Troubleshooting for details.

Embed reference

Custom element attributes

<voiceai-widget id="…" host="…" hide-launcher></voiceai-widget>
AttributeRequiredDescription
idYesThe widget ID from the dashboard (Web Widget tab). Public identifier - safe to expose in page source.
hostYesBackend host serving the widget config and APIs (your domain or white-label domain). A bare host is auto-prefixed with https://.
hide-launcherNoThe Headless Mode switch. Boolean attribute (presence = on). The widget mounts and runs but renders no UI of any kind - no launcher, no call panel, no chat window, no consent popup, not even during an active call.
Local development only: an additional http-only="true" attribute keeps a protocol-less host on http:// instead of upgrading to https://. Never use it in production.

Programmatic init (alternative to the custom element)

If you prefer to initialize the widget from JavaScript instead of placing the custom element in your markup:
const instance = window.VoiceAIWidget.init('container-element-id', {
  id: 'YOUR_WIDGET_ID',
  host: 'your-domain.com',
  hideLauncher: true,
});
// instance has: destroy(), startCall(), stopCall(), sendChatMessage(text),
//               endChatSession(), acceptConsent()
Events are dispatched on the container element you passed to init().

Global conveniences

window.VoiceAIWidget.startCall(), stopCall(), sendChatMessage(text), endChatSession(), and acceptConsent() proxy to the first mounted widget instance. Convenient for single-widget pages; with multiple widgets, call methods on the specific element instead.

Multiple widgets per page

Supported. Each <voiceai-widget> element is independent - methods are called on the element, events fire on the element. The globals target the first instance only.

Using a framework?

No framework bindings are needed - grab the element (with a ref or querySelector), call methods on it, and add event listeners. The API is plain DOM, so it works identically in React, Vue, Next.js, Svelte, and Webflow custom code.
In single-page apps, removing the <voiceai-widget> element from the DOM destroys the instance and ends active sessions. If conversations must survive client-side navigation, mount the element once at the layout level.

Next steps

Methods reference

Every method, including edge behavior and the readiness model.

Events reference

All events with payloads and ordering guarantees.