> ## Documentation Index
> Fetch the complete documentation index at: https://docs.voiceaiwrapper.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Quick start

> Go from embed snippet to your own call button in five minutes

## Before you start

<Warning>
  Headless Mode is available on our Pro tiers only. If your account is on a lower tier, upgrade your plan from the [dashboard](https://dashboard.voiceaiwrapper.app) before following this guide.
</Warning>

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.

<Steps>
  <Step title="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.

    ```html theme={null}
    <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>
    ```
  </Step>

  <Step title="Make any element the trigger">
    Any button, link, or image on your page can start a call or send a chat message.

    ```html theme={null}
    <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>
    ```

    <Note>
      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.
    </Note>
  </Step>

  <Step title="React to events">
    The widget reports everything through standard DOM events on the `<voiceai-widget>` element. Use them to drive your UI state.

    ```js theme={null}
    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();
    });
    ```
  </Step>

  <Step title="Handle errors">
    In Headless Mode your UI is the only UI - there is no widget surface to display problems. At minimum, handle these:

    ```js theme={null}
    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));
    ```
  </Step>
</Steps>

<Warning>
  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](/documentation/widget/headless-mode/troubleshooting) for details.
</Warning>

## Embed reference

### Custom element attributes

```html theme={null}
<voiceai-widget id="…" host="…" hide-launcher></voiceai-widget>
```

| Attribute       | Required | Description                                                                                                                                                                                                               |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`            | Yes      | The widget ID from the dashboard (Web Widget tab). Public identifier - safe to expose in page source.                                                                                                                     |
| `host`          | Yes      | Backend host serving the widget config and APIs (your domain or white-label domain). A bare host is auto-prefixed with `https://`.                                                                                        |
| `hide-launcher` | No       | **The 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. |

<Note>
  **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.
</Note>

### 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:

```js theme={null}
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.

<Note>
  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.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Methods reference" icon="square-code" href="/documentation/widget/headless-mode/methods">
    Every method, including edge behavior and the readiness model.
  </Card>

  <Card title="Events reference" icon="radio" href="/documentation/widget/headless-mode/events">
    All events with payloads and ordering guarantees.
  </Card>
</CardGroup>
