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

# Web Chat Widget

<video controls style={{ width: "100%", borderRadius: "0.5rem" }} src="https://mintcdn.com/blandai/yNGm7ZgdvbHHXay6/tutorials/tutorials-assets/final_web_chat_video.mp4?fit=max&auto=format&n=yNGm7ZgdvbHHXay6&q=85&s=e7db5420f5171843472ca61e4ca3dd6b" data-path="tutorials/tutorials-assets/final_web_chat_video.mp4">
  <track kind="captions" />
</video>

In just a few minutes, you'll learn how to add powerful AI chat capabilities to enhance your user experience. You can set up the agent with a pathway ID or use a agent prompt. To get started with creating a widget, head over to our [Bland Dashboard](https://app.bland.ai/dashboard/web-widget).

## Installation

You can create a web widget via the [API](/api-v1/post/widget), or through the [dashboard](https://app.bland.ai/dashboard/web-widget). Once you've created your widget, you should have an ID which you can pass into the following code snippet:

```html theme={null}
<script>
  window.blandSettings = {
    widget_id: "b5f3fb6b-2285-4c73-95be-6609eca09986", // Replace with your widget ID
  };
</script>
<script src="https://widget.bland.ai/loader.js" defer></script>
```

Once you've added this code snippet to the `<head>` of your website, you should see the chat widget load. You can customize the widgets appearance via the `config` property, or by using our visual editor on the platform.

Here is a quick video tutorial of how to create and configure a new web widget on the platform:

<video controls style={{ width: "100%", borderRadius: "0.5rem" }} src="https://mintcdn.com/blandai/Qv8uzIiZ4AVLAKGO/tutorials/tutorials-assets/widget_overview.mp4?fit=max&auto=format&n=Qv8uzIiZ4AVLAKGO&q=85&s=7fac27af5f6c2cb5dc6a2268329adc1b" data-path="tutorials/tutorials-assets/widget_overview.mp4">
  <track kind="captions" />
</video>

**Passing Additional Data (Optional)**

You can pass custom data to your widget at initialization using the `request_data` setting. This is useful for personalizing conversations with user information:

```html theme={null}
<script>
  window.blandSettings = {
    widget_id: "b5f3fb6b-2285-4c73-95be-6609eca09986",
    request_data: {
      user_id: "12345",
      first_name: "John",
      last_name: "Doe",
    },
  };
</script>
<script src="https://widget.bland.ai/loader.js" defer></script>
```

Now this information will be available to your agent as variables.

## Advanced: Custom Components

When using pathways in your widget, you can create custom components that display at specific points in the conversation. These components are embedded as iframes within the widget window.

<video controls style={{ width: "100%", borderRadius: "0.5rem" }} src="https://mintcdn.com/blandai/Qv8uzIiZ4AVLAKGO/tutorials/tutorials-assets/widget_components.mp4?fit=max&auto=format&n=Qv8uzIiZ4AVLAKGO&q=85&s=8ef9613ab98fd5dd8f4deb1dba222013" data-path="tutorials/tutorials-assets/widget_components.mp4">
  <track kind="captions" />
</video>

### Passing data to custom components

You can send agent variables to your custom components to personalize the user experience. These variables are passed as query parameters to your iframe URL.

**Setting up variables:**

Define which variables to pass in one of two ways:

* Through the platform UI in the custom component settings
* Via the `variables` field in the [widget custom components API](/api-v1/post/widget)

**Accessing variables in your component:**

The variables are automatically appended as query parameters to your component's URL.

You can access these variables using standard JavaScript:

```javascript theme={null}
// Example: Retrieving and displaying the user's first name
const params = new URLSearchParams(window.location.search);
const firstName = params.get("firstName");

// Use the variable to personalize your component
document.getElementById("greeting").textContent = `Hello, ${firstName}!`;
```

### Creating Interactive Custom Components

Your custom component can send messages back to the widget using window events.

Example: Creating a button that sends a message

```javascript theme={null}
// This code snippet is on the webpage of the custom component
<button onclick="parent.window.postMessage('widget-message:Hello World!', '*')">
  Send Message
</button>
```

> **Note**: All messages must be prefixed with `widget-message:`. The text after this prefix becomes the user message content.
>
> For example, an event `widget-message:Hello World!` will create a user message with the content "Hello World!". `widget-message:Option A selected` will create a user message with the content "Option A selected".

## Widget Bland Settings Options

The chat widget can be customized using the following configuration options:

| Option                       | Type    | Default  | Description                                                                                                                |
| ---------------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `widget_id`                  | string  | Required | The ID of the widget you want to use on your website                                                                       |
| `request_data`               | string  | -        | A record of variables you want to pass to the agent                                                                        |
| `default_chat`               | boolean | false    | If true, the widget will open on the chat by default instead of the channel menu                                           |
| `visitor_id`                 | string  | -        | Use a custom visitor ID to identify the user vs the default cookie based visitor ID, which expires after 1 week            |
| `enable_widget_state_events` | boolean | false    | Emits `bland_widget_state_change` window events on open/close/resize, enabling parent page coordination in embedded setups |
| `draggable`                  | boolean | false    | If true, the widget button will be draggable                                                                               |

***

## Escalate to Live Agents

<Note>This feature only works on [pathways](/tutorials/pathways).</Note>

Connect your Bland widget to any live agent platform (Zendesk, Intercom, Freshdesk, or your own custom solution) via webhooks. When the AI determines a human is needed, it seamlessly hands off the conversation with full context.

### Setup

<Steps>
  <Step title="Configure the Pathway Transfer Node">
    In your pathway, add a **Pathway Transfer Node** and select **Live Agent Transfer** as the transfer type. Enter the webhook URL where Bland should send conversation data.
  </Step>

  <Step title="Enable Webhook Signing">
    Webhook signing is **required** for this integration. Bland signs all webhook payloads with HMAC, sent via the `X-Bland-Signature` header. Use your signing secret to verify requests originate from Bland and haven't been tampered with.

    See [Webhook Signing](/tutorials/webhook-signing#verifying-webhooks) for implementation details.
  </Step>

  <Step title="Handle Incoming Webhooks">
    Your endpoint will receive two types of payloads from Bland: the initial conversation handoff, and subsequent user messages.
  </Step>

  <Step title="Send Messages Back to the User">
    Use the [Send Live Agent Message](/api-v1/post/widget-thread-webhook) endpoint to send responses from your live agent to the user. When the conversation is complete, send an `END_CONVERSATION` payload to close the session.
  </Step>
</Steps>

### Webhook Payloads

#### `INITIAL_CONVERSATION`

Sent when the transfer is triggered. Contains the full conversation history and all agent variables.

```json theme={null}
{
  "type": "INITIAL_CONVERSATION",
  "thread_id": "0e96f65a-244b-45c9-9342-487f548195dd",
  "sender_id": "ec14ee83-fa2b-493a-b009-0e29414a1e97",
  "language": "es",
  "variables": {
    "now_utc": "Wednesday, December 10, 2025 7:43 PM",
    "now": "Wednesday, December 10, 2025 11:43 AM",
    "today": "Wed, Dec 10, 2025",
    "first_name": "James",
    "age": 27
  },
  "conversation_history": [
    {
      "role": "user",
      "content": "Hello!",
      "original_content": "¡Hola!"
    },
    {
      "role": "agent",
      "content": "Hello! What type of specialist would you like to make an appointment with?",
      "original_content": "¡Hola! ¿Con qué tipo de especialista te gustaría concertar una cita?"
    }
  ],
  "created_at": "2025-12-10T19:43:29.704Z"
}
```

| Field                                     | Description                                                                                                 |
| ----------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `thread_id`                               | Unique identifier for this conversation. Use this to correlate subsequent messages.                         |
| `sender_id`                               | The visitor's unique identifier.                                                                            |
| `language`                                | The detected language code (e.g., `es` for Spanish).                                                        |
| `variables`                               | All agent variables at the time of transfer, including any `request_data` you passed during initialization. |
| `conversation_history`                    | Array of all messages exchanged before the handoff.                                                         |
| `conversation_history[].content`          | The message content (translated to English if applicable).                                                  |
| `conversation_history[].original_content` | The original message in the user's language. Only included when translation occurred.                       |

#### `MESSAGE`

Sent for each message the user sends after the initial handoff.

```json theme={null}
{
  "type": "MESSAGE",
  "thread_id": "0e96f65a-244b-45c9-9342-487f548195dd",
  "sender_id": "ec14ee83-fa2b-493a-b009-0e29414a1e97",
  "content": "Hello?",
  "original_content": "¿Hola?",
  "created_at": "2025-12-10T19:44:04.002Z"
}
```

| Field              | Description                                                                                          |
| ------------------ | ---------------------------------------------------------------------------------------------------- |
| `thread_id`        | Same identifier from the initial conversation, allowing you to match messages to the correct thread. |
| `content`          | The message content (translated to English if applicable).                                           |
| `original_content` | The original message in the user's language. Only included when translation occurred.                |

***

## Post-Conversation Webhooks

Receive a webhook notification when a widget conversation ends. This is useful for logging conversations, triggering follow-up workflows, or syncing conversation data to your CRM.

Post-conversation webhooks are signed using HMAC. The signature is included in the `X-Webhook-Signature` header. See [Webhook Signing](/tutorials/webhook-signing#verifying-webhooks) for verification details.

### Configuration

Configure post-conversation webhooks in two ways:

* **Dashboard**: Set the webhook URL in your widget settings on the platform
* **API**: Pass `webhook_url` when [creating](/api-v1/post/widget) or [updating](/api-v1/patch/widget-id) your widget

By default, a conversation times out after **24 hours** of user inactivity. You can customize this via the dashboard or using `config.timeoutSeconds`:

```json theme={null}
{
  "config": {
    "timeoutSeconds": 3600 // 1 hour
  }
}
```

### Webhook Payload

When a conversation ends, Bland sends a POST request to your configured webhook URL with the following payload:

```json theme={null}
{
  "widget_id": "b5f3fb6b-2285-4c73-95be-6609eca09986",
  "thread_id": "ba2cf109-4fb1-4fa5-8146-e085c9009d02",
  "visitor_id": "6eedb0cc-110f-45ed-9c05-5a66c03328f1",
  "pathway_id": "e98ef308-1380-4bea-bdfa-da54a542b12c",
  "thread_created_at": "2025-12-17T14:51:22.411Z",
  "thread_ended_at": "2025-12-17T14:51:24.655Z",
  "conversation_history": [
    {
      "id": "9b105ed8-aee3-48f1-b067-85ad5a8436c9",
      "sender_type": "ASSISTANT",
      "sender_id": null,
      "content": "Hey, thanks for calling! To get started, could you tell me your name and what kind of doctor you’d like to schedule an appointment with?",
      "original_content": null
    },
    {
      "id": "cd83215d-a7a5-4bb9-bd93-ab353f2dcb48",
      "sender_type": "USER",
      "sender_id": "6eedb0cc-110f-45ed-9c05-5a66c03328f1",
      "content": "hi what's up?",
      "original_content": null
    }
  ],
  "end_reason": "Conversation timed out after 3600 seconds of inactivity"
}
```

| Field                   | Description                                                                                                                                             |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `widget_id`             | The widget's unique identifier.                                                                                                                         |
| `thread_id`             | Unique identifier for this conversation thread.                                                                                                         |
| `visitor_id`            | The visitor's unique identifier.                                                                                                                        |
| `pathway_id`            | The pathway associated with this widget (if applicable).                                                                                                |
| `thread_created_at`     | ISO timestamp when the conversation started.                                                                                                            |
| `thread_ended_at`       | ISO timestamp when the conversation ended.                                                                                                              |
| `live_agent_handoff_at` | ISO timestamp when the conversation was handed off to a live agent, or `null` if no handoff occurred.                                                   |
| `conversation_history`  | Array of all messages in the conversation. If messages have been translated after a live agent handoff, the `original_content` field will be populated. |
| `end_reason`            | The reason the conversation ended, which usually is either a timeout, or the pathway hit a terminal node.                                               |

***

Docs for agents: [llms.txt](/llms.txt)
