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

# Add to Website

> Add your Dograh agent to any website so visitors can talk to it by voice or chat with it by text.

### How to add it

Add your agent to any website using the Configure Widget dialog in your agent's settings.

Step 1: Open the agent settings by clicking the gear icon in the top-right of the agent editor.

<img src="https://mintcdn.com/dograhai/jQOj93A5ymzrZEmI/images/open-settings.png?fit=max&auto=format&n=jQOj93A5ymzrZEmI&q=85&s=324ed3c9eeacf56aa025d8816e2fe1c3" alt="Open agent settings" width="2880" height="1557" data-path="images/open-settings.png" />

Step 2: Scroll to the **Add to Website** section and click **Configure Widget**.

<img src="https://mintcdn.com/dograhai/P80S5itzSvWa2Xtq/images/add-to-website.png?fit=max&auto=format&n=P80S5itzSvWa2Xtq&q=85&s=49ef00c06cb77cf4956c62f4b2891fd9" alt="Go to Add to Website" width="2850" height="1558" data-path="images/add-to-website.png" />

Step 3: Enable embedding, add your website's domain to **Allowed Domains**, choose a **Widget Type** (Voice or Chat) and an embed mode (**Floating Widget**, **Inline Component**, or **Headless (Bring Your Own UI)**), customize the button (position, color, text) if applicable, and click **Save Configurations**.

<img src="https://mintcdn.com/dograhai/P80S5itzSvWa2Xtq/images/save-configurations.png?fit=max&auto=format&n=P80S5itzSvWa2Xtq&q=85&s=a05fbfaac2994edfa18993358aabc38e" alt="Save configurations" width="1974" height="1534" data-path="images/save-configurations.png" />

Step 4: Copy the generated embed code and paste it into your web page to test your agent.

<img src="https://mintcdn.com/dograhai/jQOj93A5ymzrZEmI/images/copy-deployment-code.png?fit=max&auto=format&n=jQOj93A5ymzrZEmI&q=85&s=35ff8fb08cacb064d7a8bd3faf970ae6" alt="Copy deployment code" width="2880" height="1537" data-path="images/copy-deployment-code.png" />

## Widget types

Each embed widget is either a voice widget or a chat widget — pick the type in the Configure Widget dialog. Both types support all three embed modes.

| Type      | How visitors interact                                                                |
| --------- | ------------------------------------------------------------------------------------ |
| **Voice** | Visitors talk to your agent over a live audio call (WebRTC, microphone required).    |
| **Chat**  | Visitors type messages in a chat panel and the agent replies as text. No microphone. |

How chat conversations behave:

* The conversation starts when the visitor opens the chat (clicks the chat button) — the agent greets them first. Page loads alone never start a conversation.
* A chat session lasts up to **1 hour**. When it expires, the visitor is offered a **Start new chat** button, which begins a fresh conversation.
* Reloading the page starts a fresh conversation on the next open — chat history isn't carried across page loads.
* Each conversation counts once toward the embed token's usage limit, same as one voice call.
* Chat conversations appear in your agent's call history with a full transcript.

## Embed modes

| Mode                 | What it renders                                                                                      | When to use                                                                                       |
| -------------------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| **Floating Widget**  | A pill-shaped CTA button anchored to a corner of the page. For chat widgets it toggles a chat panel. | You want a turn-key experience that doesn't disturb your existing layout.                         |
| **Inline Component** | A panel rendered inside a `<div id="dograh-inline-container">` that you place in your page.          | You want the agent embedded in a specific section (landing-page hero, support tab, etc.).         |
| **Headless**         | No UI. Only the audio/chat pipeline plus a JavaScript API on `window.DograhWidget`.                  | You want full control over the UI — your own buttons, design system, framework state, animations. |

## Prerequisites

These apply to all three modes:

* **Voice widgets:** serve your page over **HTTPS** or from `http://localhost`. Browsers refuse microphone access on plain HTTP origins or `file://`. Chat widgets have no microphone requirement, though HTTPS is still recommended.
* If you set **Allowed Domains** in the dashboard, include your test origin (e.g. `localhost`) — otherwise the widget's requests are rejected. Leave the list empty to allow all domains.
* The embed snippet you copy from the dashboard is a single `<script>` tag that loads `dograh-widget.js` **asynchronously**. The widget auto-initializes once it loads and exposes `window.DograhWidget`. Code that registers callbacks must wait for the widget to be available.

## Pass context to the agent

Your page usually knows something about the visitor — their name, plan, cart value, the article they were reading. Pass it along and your agent can use it from the first word.

<Warning>
  Context Key names cannot contain dots, whitespace, pipes, or braces because those
  characters have structural meaning in template expressions. Invalid entries are
  dropped without preventing the conversation from starting.
</Warning>

The snippet you copy from the dashboard carries a `data-dograh-context` attribute — a JSON object of details about the visitor. The snippet is a small bootstrap function: `js` is the widget `<script>` element it creates, and the context is attached to that element before it is added to the page. The relevant part of the generated snippet looks like this (keep the generated `js.src` value, which contains your embed token):

```html theme={null}
<script>
  (function(d, s, id) {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) return;
    js = d.createElement(s);
    js.id = id;
    js.src = '<dashboard-generated widget URL>';
    js.setAttribute('data-dograh-context', JSON.stringify({
      page_url: window.location.href,
      today: new Date().toISOString().slice(0, 10)
    }));
    js.async = true;
    fjs.parentNode.insertBefore(js, fjs);
  }(document, 'script', 'dograh-widget'));
</script>
```

Because it's built in JavaScript at page load, you can put anything your page knows in it — a logged-in customer's name, their plan, cart contents. Replace the object inside `JSON.stringify(...)` in the generated snippet, for example:

```js theme={null}
{
  customer_name: currentUser.firstName,
  plan: currentUser.plan,
  cart: { items: cart.length, total: cart.total }
}
```

Each key is then available in any node prompt as `{{initial_context.<name>}}`:

```text theme={null}
Greet {{initial_context.customer_name | there}} and mention their {{initial_context.plan}} plan.
```

Values can be strings, numbers, booleans, or nested objects. This works for voice and chat widgets alike, and the values are recorded on the conversation so you can see what the agent was given.

### Update context after the page loads

The attribute is fixed at page load, which doesn't fit a single-page app — the visitor logs in, changes route, or fills a cart long after the snippet ran. For that, call `setContext()`:

```js theme={null}
window.DograhWidget.setContext({
  customer_name: user.firstName,
  plan: user.plan
});
```

Each call merges into the context already collected, so you can add details as they arrive and re-send a name to correct it. `getContext()` returns the current set.

Context is read when a conversation starts, so `setContext()` applies to the **next** conversation — calling it mid-call or mid-chat doesn't change the one in progress (the widget logs a console warning if you do). For chat widgets, "next" includes the fresh conversation started by **Start new chat** after a session expires.

<Note>
  The widget script loads asynchronously, so `window.DograhWidget` may not exist yet when your app's code first runs. Call `setContext()` from an event that fires after load — a `window.load` listener, or a user action like clicking your own "Chat with us" button. See [Lifecycle callbacks](#lifecycle-callbacks-all-modes) for the same timing rule.
</Note>

Use whichever fits: `data-dograh-context` for what the page knows at render, `setContext()` for what it learns later. They merge, and `setContext()` wins on a repeated name.

<Warning>
  Context comes from the page, so a visitor can both read it and change it before it reaches your agent. Never pass secrets, and don't let it gate what the agent will do or disclose — treat `plan: "pro"` as a hint for phrasing, not proof of entitlement. For data the agent must trust, pass an opaque id like `customer_id` and let Dograh fetch the real details from your API with [Pre-Call Data Fetch](/voice-agent/pre-call-data-fetch).
</Warning>

Limits, applied per conversation: up to 50 variables, 64 characters per name, 2000 characters per value, and 8 KB in total. Anything past a limit is dropped and the conversation still starts. The names `provider` and `runtime_configuration` are reserved and ignored.

## Floating Widget

<img src="https://mintcdn.com/dograhai/jQOj93A5ymzrZEmI/images/floating-widget-example.png?fit=max&auto=format&n=jQOj93A5ymzrZEmI&q=85&s=5ba7b809c8bcb309252105d47ecb9ee0" alt="Floating widget shown in the corner of a host page" width="2880" height="1555" data-path="images/floating-widget-example.png" />

Renders a pill-shaped button anchored to a corner of the page.

* **Voice:** clicking the button (microphone icon + text) starts a call; clicking again ends it. The button auto-updates its label and color across the call lifecycle: configured text → "Connecting…" → "End Call" → "Retry" on failure.
* **Chat:** clicking the button (chat icon + text) opens a chat panel anchored to the same corner; the agent greets the visitor and the conversation happens in the panel. Clicking the button (or the panel's ×) closes the panel without ending the conversation — reopening shows the same transcript.

Configure **Button Text**, **Button Color**, and **Position** (top/bottom + left/right) from the dashboard.

The host page writes no JavaScript — pasting the embed snippet is the entire integration. If you want to subscribe to call lifecycle events (e.g. analytics), see [Lifecycle callbacks](#lifecycle-callbacks-all-modes) below

## Inline Component

<img src="https://mintcdn.com/dograhai/jQOj93A5ymzrZEmI/images/inline-widget-example.png?fit=max&auto=format&n=jQOj93A5ymzrZEmI&q=85&s=0e09a3c610e5a7e14e6bb5b949e95c07" alt="Inline widget rendered inside a page section" width="2844" height="1555" data-path="images/inline-widget-example.png" />

Renders a panel inside a `<div>` you place in your page.

* **Voice:** a status panel (status icon + status text + CTA button). Status changes update the panel in place.
* **Chat:** a call-to-action screen first; clicking the button replaces it with a chat panel that fills the container. No extra JavaScript is needed.

Configure **Button Text**, **Button Color**, and **Call to Action Text** from the dashboard.

### Plain HTML

Place a container `<div>` where you want the widget to render. The widget auto-attaches to it.

```html theme={null}
<!-- Paste the dograh embed snippet from the dashboard somewhere on the page -->
<div id="dograh-inline-container"></div>
```

### React

Because React mounts after the widget script may have already loaded, integrate via `initInline` on first mount and `refresh` on remount. Poll for `window.DograhWidget` to handle the async script load.

```tsx theme={null}
import { useEffect } from 'react';

declare global {
  interface Window {
    DograhWidget?: {
      initInline: (options: { container: HTMLElement }) => void;
      refresh: () => void;
      getState: () => { isInitialized: boolean };
    };
  }
}

export function Assistant() {
  useEffect(() => {
    let retries = 0;
    const tryInit = () => {
      const container = document.getElementById('dograh-inline-container');
      if (window.DograhWidget && container) {
        const { isInitialized } = window.DograhWidget.getState();
        if (isInitialized) window.DograhWidget.refresh();
        else window.DograhWidget.initInline({ container });
      } else if (retries++ < 50) {
        setTimeout(tryInit, 100);
      }
    };
    tryInit();
  }, []);

  return <div id="dograh-inline-container" />;
}
```

## Headless Mode

<img src="https://mintcdn.com/dograhai/jQOj93A5ymzrZEmI/images/headless-widget-example.png?fit=max&auto=format&n=jQOj93A5ymzrZEmI&q=85&s=5526b5605820488f25eeecac60cbf41f" alt="Headless widget driven by host-page UI" width="2842" height="1548" data-path="images/headless-widget-example.png" />

In Headless mode the widget injects no UI of its own. You render whatever buttons, banners, or chat interfaces you want, and drive the agent through the JavaScript API.

### JavaScript API (voice widgets)

| Method / Callback                            | Description                                                                                                                                          |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `window.DograhWidget.start()`                | Begin a voice call. Must be called from inside a user-gesture handler (e.g. `click`) so the browser grants microphone access.                        |
| `window.DograhWidget.end()`                  | End the active call.                                                                                                                                 |
| `window.DograhWidget.onCallStart(cb)`        | Fires when `start()` is invoked (status `connecting`). No payload.                                                                                   |
| `window.DograhWidget.onCallConnected(cb)`    | Fires when the WebRTC connection is established. Payload: `{ agentId, workflowRunId, token }`.                                                       |
| `window.DograhWidget.onCallDisconnected(cb)` | Fires only if the call had connected, when teardown runs. Payload: `{ agentId, workflowRunId, token, durationSeconds }`.                             |
| `window.DograhWidget.onCallEnd(cb)`          | Fires whenever the call session is torn down (including failed-to-connect attempts). No payload.                                                     |
| `window.DograhWidget.onStatusChange(cb)`     | Fires on every status change. Callback receives `(status, text, subtext)`. Status values: `idle`, `connecting`, `connected`, `failed`.               |
| `window.DograhWidget.onError(cb)`            | Fires on errors (mic permission denied, server error, etc.). Callback receives an `Error` object.                                                    |
| `window.DograhWidget.setContext(vars)`       | Merge visitor context for the next call — see [Pass context to the agent](#pass-context-to-the-agent). Works in every embed mode, not just headless. |

All `on*` setters are single-listener — calling the same one again replaces the previous handler.

### JavaScript API (chat widgets)

| Method / Callback                           | Description                                                                                                                                                  |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `window.DograhWidget.startChat()`           | Start a conversation. The agent's greeting arrives via `onMessage`.                                                                                          |
| `window.DograhWidget.sendMessage(text)`     | Send a visitor message. Returns a Promise that resolves with the updated transcript (array of turns), or `null` if the message couldn't be delivered.        |
| `window.DograhWidget.getMessages()`         | Current transcript as an array of turns: `{ id, status, user_message, assistant_message }`, each message being `{ text, created_at }`.                       |
| `window.DograhWidget.onMessage(cb)`         | Fires once per new agent reply. Callback receives `(text, turn)`.                                                                                            |
| `window.DograhWidget.onChatStateChange(cb)` | Fires on every chat state change. States: `idle`, `starting`, `ready`, `waiting` (agent is replying), `ended`, `expired`, `error`.                           |
| `window.DograhWidget.onError(cb)`           | Fires on errors. Callback receives an `Error` object.                                                                                                        |
| `window.DograhWidget.setContext(vars)`      | Merge visitor context for the next conversation — see [Pass context to the agent](#pass-context-to-the-agent). Works in every embed mode, not just headless. |

In chat mode `start()` aliases `startChat()` and `end()` is a no-op teardown (chat sessions need none), so generic snippets keep working. Sends are serialized — `sendMessage` while a reply is pending (`waiting`) resolves to `null`.

```html theme={null}
<button id="open-chat">Chat with us</button>
<div id="transcript"></div>
<input id="chat-input" /><button id="send-btn">Send</button>

<script>
  window.addEventListener('load', () => {
    window.DograhWidget.onMessage((text) => {
      const p = document.createElement('p');
      p.textContent = 'Agent: ' + text;
      document.getElementById('transcript').appendChild(p);
    });

    document.getElementById('open-chat').addEventListener('click', () => {
      window.DograhWidget.startChat();
    });

    document.getElementById('send-btn').addEventListener('click', async () => {
      const input = document.getElementById('chat-input');
      const p = document.createElement('p');
      p.textContent = 'You: ' + input.value;
      document.getElementById('transcript').appendChild(p);
      await window.DograhWidget.sendMessage(input.value);
      input.value = '';
    });
  });
</script>
```

<Note>
  **About timing.** The widget script loads asynchronously, so `window.DograhWidget` may not exist at the moment your inline `<script>` first runs. The examples below assume `window.DograhWidget` is already available when registration runs. To guarantee that:

  * **Vanilla JS:** wrap your registration code in `window.addEventListener('load', () => { /* register here */ })`.
  * **React:** inside `useEffect`, register immediately if `document.readyState === 'complete'`, otherwise add a one-time `window.load` listener that registers on fire.
  * **Click handlers** that call `start()` / `end()` don't need a guard — by the time a user clicks, the widget has long since loaded.
</Note>

### Vanilla JS

```html theme={null}
<button id="talk-btn">Talk to AI</button>

<script>
  let callStatus = 'idle';
  const btn = document.getElementById('talk-btn');

  function render() {
    btn.textContent =
      callStatus === 'connected' ? 'End Call'
      : callStatus === 'connecting' ? 'Connecting…'
      : callStatus === 'failed' ? 'Retry'
      : 'Talk to AI';
  }

  window.DograhWidget.onStatusChange((status) => {
    callStatus = status;
    render();
  });

  window.DograhWidget.onError((err) => {
    console.error('Dograh error:', err.message);
  });

  btn.addEventListener('click', () => {
    if (callStatus === 'connected' || callStatus === 'connecting') {
      window.DograhWidget.end();
    } else {
      window.DograhWidget.start();
    }
  });
</script>
```

### React + TypeScript

```tsx theme={null}
import { useEffect, useState } from 'react';

type CallStatus = 'idle' | 'connecting' | 'connected' | 'failed';

declare global {
  interface Window {
    DograhWidget: {
      start: () => void;
      end: () => void;
      onStatusChange: (cb: (status: CallStatus, text?: string, subtext?: string) => void) => void;
      onError: (cb: (err: Error) => void) => void;
    };
  }
}

export function TalkButton() {
  const [status, setStatus] = useState<CallStatus>('idle');

  useEffect(() => {
    window.DograhWidget.onStatusChange((s) => setStatus(s));
    window.DograhWidget.onError((err) => console.error('Dograh error:', err.message));
  }, []);

  const isLive = status === 'connected' || status === 'connecting';
  const label = { idle: 'Talk to AI', connecting: 'Connecting…', connected: 'End Call', failed: 'Retry' }[status];

  return (
    <button onClick={() => (isLive ? window.DograhWidget.end() : window.DograhWidget.start())}>
      {label}
    </button>
  );
}
```

<Note>
  `start()` must run inside a real user-gesture handler (`click`, `touchend`, etc.). Browsers refuse to grant microphone access to scripts that request it outside of one — calling `start()` from a `setTimeout` or on page load will fail with a permission error.
</Note>

## Lifecycle callbacks (all modes)

The `on*` callbacks in the [Headless JavaScript API](#javascript-api-voice-widgets) work in **all three embed modes**, not just Headless. Use them for analytics or to trigger UI in the host page even when the widget is rendering its own UI (Floating or Inline). The call callbacks (`onCall*`) fire for voice widgets; for chat widgets use `onMessage` and `onChatStateChange` the same way.

```js theme={null}
window.DograhWidget.onCallConnected(({ agentId, workflowRunId }) => {
  analytics.track('voice_call_started', { agentId, workflowRunId });
});

window.DograhWidget.onCallDisconnected(({ workflowRunId, durationSeconds }) => {
  analytics.track('voice_call_ended', { workflowRunId, durationSeconds });
});
```

`onCallConnected` and `onCallDisconnected` only fire when the call actually establishes a media connection — failed-to-connect attempts (e.g. denied mic, network failure) don't trigger them, so analytics stay clean.
