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

# Consent in Headless Mode

> Show your own consent UI when "Require consent" is enabled on a hidden widget

Widgets can have **"Require consent"** enabled - a legal popup the visitor must accept before conversations start. Consent is stored in `localStorage` under the widget's configured key.

* **Normal (visible) mode:** the widget shows its built-in consent popup; there is nothing for you to do.
* **Headless Mode:** the widget renders nothing, so it cannot show its popup. Instead, it hands the consent step to you.

<Warning>
  In Headless Mode, **displaying the consent text is your responsibility.** If you have legal requirements for AI-call disclosure or recording notices, your own consent UI must satisfy them.
</Warning>

## The flow

<Steps>
  <Step title="The action pauses">
    The visitor triggers `startCall()` or `sendChatMessage()`. Because consent hasn't been given yet, the widget **pauses** the action instead of executing it.
  </Step>

  <Step title="widget.consent-required fires">
    The widget dispatches `widget.consent-required` with `{ pendingAction: 'voice' | 'chat' }` so you know what the visitor was trying to do.
  </Step>

  <Step title="You show your own consent UI">
    A modal, a checkbox, an inline banner - whatever fits your design.
  </Step>

  <Step title="Accept or decline">
    On accept, call `widget.acceptConsent()`: consent is persisted and **the paused action resumes automatically** - the call starts, or the queued message sends. On decline, do nothing; the action is simply dropped.
  </Step>
</Steps>

## Example

```js theme={null}
const widget = document.querySelector('voiceai-widget');
const modal = document.getElementById('consent-modal');

widget.addEventListener('widget.consent-required', (e) => {
  // e.detail.pendingAction is 'voice' or 'chat' - useful for tailoring the copy
  modal.classList.add('show');
});

document.getElementById('consent-accept').onclick = () => {
  modal.classList.remove('show');
  widget.acceptConsent(); // the paused call/message resumes automatically
};

document.getElementById('consent-decline').onclick = () => {
  modal.classList.remove('show'); // declining = simply not calling acceptConsent()
};
```

## Good to know

* **Asked once per browser.** Consent is persisted in `localStorage`, so subsequent visits skip straight to the action.
* **One flow covers both surfaces.** A single consent acceptance covers both voice and chat - you don't ask twice.
* **`acceptConsent()` is always safe.** If consent wasn't requested, the call is a no-op.

<Info>
  The [voice-and-chat example](/documentation/widget/headless-mode/examples) includes a complete consent modal serving both surfaces.
</Info>
