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

# Web widget

> One script tag puts the same agent that answers WhatsApp on your own website.

The web widget is a chat launcher you add to your site with a single script tag.
A visitor clicks it, types, and is answered by **the same agent** that runs your
outreach and answers [inbound WhatsApp](/inbound) — same knowledge base, same
policies, same voice, and the same shared inbox when it hands off to a person.

Nothing about it is a separate support bot with its own configuration to keep in
sync. What is different from WhatsApp is who the visitor is: nobody signs in, so
there is no phone number and no email address until the conversation produces
one.

## Install

Copy the snippet from the Boom app — **Settings → Channels → Web widget →** your
widget — and paste it before the closing `</body>` tag of every page that should
show the widget. It looks like this:

```html theme={null}
<script async src="https://www.useboom.ai/widget/v1/w/bw_live_YOUR_INSTALL_KEY"></script>
```

Copy it rather than typing it. The origin in the snippet is pinned by the
deployment you are looking at, so a hand-written one will point at the wrong
environment.

The install key in that URL is **public** — it ships in your page source, and it
has to. It identifies the widget; it does not authorize anything. What controls
where your widget may be embedded is the origin list below.

<Note>
  `async` is deliberate. The script does not block your page, and the launcher
  button appears a moment after the rest of the page has painted.
</Note>

## Allow your site's origin

A widget only renders on origins you list, and the list starts empty. Add every
origin that will host it under **Allowed origins** on the same settings page.

Matching is **exact string equality**, on the origin a browser actually sends:

| Add | Not |
| - | - |
| `https://example.com` | `example.com` (no scheme) |
| `https://www.example.com` — separately, if you serve both | `*.example.com` (no wildcards) |
| `https://staging.example.com` | `https://example.com/` (no paths) |

So `https://example.com` does **not** cover `https://www.example.com`, and a
staging domain needs its own entry.

<Warning>
  A widget with an empty origin list installs cleanly and never opens. The
  launcher button appears — the script that draws it does not know which site
  loaded it — and clicking it produces an empty panel. If that is what you are
  seeing, this list is the first thing to check.
</Warning>

## If your site sets a Content-Security-Policy

Two directives need our origin. Add them to whatever your site already sends:

```
script-src  https://www.useboom.ai
frame-src   https://www.useboom.ai
```

`script-src` covers both the snippet above and the launcher bundle it loads.
`frame-src` covers the chat panel, which is an iframe served from our origin.

You do **not** need a `connect-src` entry. The panel makes its own network
requests, but it makes them from inside that iframe, so they are governed by our
policy rather than your page's. The launcher itself — the button and the greeting
bubble, which are the only parts living in your document — makes no requests at
all.

Serve the page over HTTPS. The widget's session cookie is `Secure`, so a visitor
on an `http://` page will not keep a conversation across reloads.

## Customize how it looks

The settings page is a live canvas with the widget drawn on a stand-in for your own
site, and the settings beside it in four groups. Switch the canvas between desktop
and mobile, and between the open panel and the collapsed launcher, to see what a
visitor gets. You pick one accent colour and Boom derives the rest, so the text drawn
on it stays legible whatever you choose.

**Launcher** — the button visitors click:

| Setting | What it changes |
| - | - |
| Icon | The glyph in the launcher circle: a speech bubble, a question mark, a sparkle, sparkles, or a robot |
| Launcher image | Your own image in the circle instead of a glyph. An `https://` URL; square reads best, since it is cropped to a circle. If it cannot be loaded the icon is used instead |
| Launcher label | The words beside the icon — your own capitalisation is kept. Keep it to a few, since the button truncates rather than growing across your page. Leave it blank for the icon on its own |
| Position and distance | Which corner the launcher sits in, and how far off the edges — room for a cookie banner or a sticky nav bar |

**Panel** — the window that opens:

| Setting | What it changes |
| - | - |
| Accent colour | The launcher, the panel header, and the visitor's own messages |
| Theme | `Light` or `Dark` |
| Panel height | `Full height` fills the visitor's window, less the distance you set. `Compact` keeps the panel to a card about 560px tall, and shrinks it further on a short window so it never runs off the bottom. On a phone both cover the screen |
| Logo | Shown in the panel header and on the avatar beside each reply. An `https://` URL |

**Words** — everything a visitor reads: the panel title, the greeting, the composer's
hint text, and up to four one-tap suggested questions shown on an empty panel.

Every piece of copy is per-language — English, Spanish, and Portuguese — and the
visitor gets the language their browser asks for, falling back to English for
anything you leave blank. A suggested question left blank in one language simply
does not appear for those visitors.

<Note>
  A suggested question **is** the message. Tapping one sends that exact text as the
  visitor's first message, so write them the way a customer would ask, and expect the
  agent to answer them the way it answers anything else — they are not scripted
  replies.
</Note>

Below the composer the panel shows a **"Powered by Boom"** link. That line is not
configurable.

<Note>
  The panel does not tell visitors on its own that they are talking to an AI. Where you
  need that disclosure — the EU AI Act asks for it wherever it is not otherwise obvious —
  put it somewhere you control. The greeting is the natural place: it is the first thing
  a visitor reads, and it is yours to write in all three languages.
</Note>

## What a visitor's session is

A visitor is anonymous. The only thing tying somebody who comes back to the
transcript they left is a cookie on our origin, set the moment they send a first
message and good for **30 days**.

That cookie is `Partitioned` (CHIPS), which has two consequences worth knowing:

* It survives in browsers that block ordinary third-party cookies, which is now
  most of them.
* It is scoped to **your** site. The same person visiting two different customers
  of ours is two unrelated visitors, and the same widget embedded on two of your
  own domains keeps two separate conversations. The widget is not a cross-site
  identity.

<Note>
  A visitor who blocks cookies for our origin can still use the widget, but every
  message starts a new conversation, and the panel is empty again after a reload.
  There is no login to fall back on.
</Note>

## Sending images

A visitor can attach an image — PNG, JPEG, GIF or WebP, up to **4 MB**, four per
message. An image on its own is a message; no caption is required.

The agent can read it. It receives a written description of the image plus any
text found in it, which is what lets it answer about a screenshot of an error, a
photo of a receipt, or a picture of the wrong item that arrived. It does not see
the picture itself, so an image whose meaning depends on fine detail is better
described in words as well.

Your agent can send images back, from the assets you have given it — the same
library it draws on for WhatsApp. So can a person replying from the shared
inbox.

<Note>
  Images only, in both directions. A PDF, a video or an audio file is refused
  rather than sent: the panel has no way to show one, and a reply that arrived
  with the file silently missing would be worse than a refusal you can see. On
  the inbox side the paperclip only offers image files for a widget
  conversation, and a reply that somehow carries anything else is marked as
  failed rather than delivered without it.
</Note>

<Warning>
  Anyone who can open your site can upload to the widget — there is no login. It
  is rate limited per visitor and per widget, every file is checked to be a real
  image regardless of what it claims to be, and nothing larger than 4 MB is
  stored. Treat what arrives as untrusted, exactly as you would a message.
</Warning>

## Controlling the widget from your page

The snippet installs one global. It works from the moment the tag runs, so you
can call it from a button in your own header without waiting for anything to
load:

```js theme={null}
Boom('open');    // also 'close' and 'toggle'
```

To start a conversation from your own UI, for example an "Ask about this order"
button, send a first message on the visitor's behalf:

```js theme={null}
button.addEventListener('click', () => {
  Boom('send', '¿Dónde está mi pedido #1234?');
});
```

`send` opens the chat and posts the text as the visitor. It only works from a
real click or keypress: called from a timer or on page load it is ignored, so no
script on your page can write to the chat without the visitor asking.

Re-running the snippet is safe. A single-page app that re-executes it on a route
change gets the widget that is already there, not a second button.

## Proactive help (copilot)

<Note>
  Proactive help is in early access. Contact us to have it turned on for your
  organization; until then, the **In-app expert** tab does not appear.
</Note>

The copilot notices when a visitor seems stuck in your app and offers help: after
30 seconds without a click, or right after they click the same button three times
without anything changing, it shows a short message next to the chat button,
labelled as an AI assistant, and can draw a ring around the button that helps.
Clicking that button works exactly as before. It can also walk a visitor
step by step toward a goal, one control at a time. The message is one you
write: the widget's **Playbooks** page holds them, each describing a
situation ("the library has no papers yet") and what to say.

You switch it on and choose how it shows up in the widget's settings, on the **In-app expert** tab:

* **Corner notice** (the default): the message appears as a card next to the
  chat button.
* **Island**: a small dark tab docked on the edge of the screen, separate from
  the chat button. Visitors can drag it to any edge. It picks the quietest way to
  say each message, from the playbook and what is on screen: a single line that
  comes out of the tab when there is nothing to point at, a ring on the control
  when the control says it all, a short card when it can guide the visitor, a
  choice of two or three when it can't tell which you meant, and only a dot while
  the visitor is typing. A situation you mark as a `warning` stays until acted
  on. Tapping the tab opens a card with the current message or step and a
  question box; sending a question opens the chat with it. With nothing pending,
  the card also offers up to three things to do on that screen: the situations
  you gave a short `label` (and a goal), and picking one starts its guide. Its cards can be
  **Light** (the default) or **Dark**, and you can add a small "How it works"
  link to your own help page.

In both styles, a guide's steps appear in a card beside the control they refer to.

It runs only when you switch it on for a widget and it has at least one
playbook. Switching it off stops it, including a guide a visitor is in the middle
of. Playbooks are written per page: each one lists the paths it applies to
(`/events/**`, `/settings`) and the situations it watches for there.

The chat and the expert are separate: in the widget's settings you can switch the
chat off and keep only the expert. The island then has no question box, since
there is no chat to hand a question to. With the chat off, the chat panel and its
message and upload endpoints refuse every request, including ones made directly
rather than through the snippet.

What it reads, from the screen the visitor is on:

* the buttons, links, tabs and form labels, and whether they are disabled
* headings and short labels
* the last few pages visited and controls clicked on this tab, kept only in the
  tab's session storage and sent only when the copilot decides whether to offer
  help

What it never reads:

* anything anyone types into a field (it only notes whether a field is empty
  or filled, and not even that for passwords)
* content inside lists and tables, where your users' own data usually lives
* anything inside an element you mark with `data-boom-private`

Emails and phone-like numbers are masked before anything leaves the browser.
Boom keeps only the page's route shape (ids removed) and the name of the control
the expert pointed to, never field values, rows, messages or anything typed. To
keep a region out entirely:

```html theme={null}
<section data-boom-private>
  <!-- account details, balances, anything personal -->
</section>
```

The copilot only answers pages on your widget's allowed origins, the same list the
chat uses.

## When nothing answers

Answering is a setting, not a default, and it resolves exactly the way
[inbound](/inbound) does: per channel, falling back to your organization.

<Note>
  If no agent is configured to answer the widget, or the configured agent is
  disabled, the visitor's message is still received and stored — it waits in the
  shared inbox for a person — and the panel stops waiting and offers a retry
  rather than spinning. Boom does not pick an agent on your behalf, because
  guessing which agent should speak for you is worse than staying quiet.
</Note>

The agent that answers is assigned to the conversation, so the inbox always shows
who is replying. Unassigning the agent from a conversation hands it to your team:
the agent stops answering that visitor, and their messages wait in the inbox. It
picks the conversation back up only after 7 days with no one on your team
touching it, or as soon as someone sends it back to the AI.

Replies a person sends from the inbox arrive in the visitor's open panel in
realtime, minutes later if that is when they are written.

## Rate limits

Per visitor, **30 messages a minute** and **10 image uploads a minute**. Per
widget, **120 first-messages** and **40 uploads** a minute from visitors who have
no session yet — set far above a real site's arrival rate, so they throttle a loop
rather than a launch. A throttled request is refused before anything is stored.

Uploads have their own allowance rather than sharing the message one, so a
visitor who has just sent several images can still write about them.

## Rotating the install key

**Rotate** on the settings page mints a new key and invalidates the old one
immediately. The snippet on your site then points at a key that no longer
resolves, and the widget stops appearing until you paste the new one — so rotate
when you are ready to deploy the change, not before.

Rotating does not touch conversations, transcripts, or appearance.

## Troubleshooting

| What you see | Almost always |
| - | - |
| No launcher button at all | The snippet is not on the page, or the install key was rotated and the page still has the old one. |
| Launcher appears, panel opens empty | Your origin is not in the allowed-origins list — check scheme, `www`, and port. |
| Panel opens, visitor's message stays put | Your CSP is missing `frame-src`, or a `script-src` gap kept the launcher's own bundle out. |
| Transcript gone after a reload | Cookies for our origin are blocked, or the page is served over `http://`. |
| Message sent, nothing ever replies | No answering agent is configured for the channel, or the one configured is off. If it is one conversation only, someone on your team unassigned it in the last 7 days. |
| "Only images can be attached" when picking a file | The file is not a PNG, JPEG, GIF or WebP — or it claims to be one and is not. |
| An image the visitor sent, and the agent answers as if it saw nothing | Reading the image failed on our side. The agent is told a file arrived but not what is in it, so it will ask rather than guess. |

## Related

<CardGroup cols={2}>
  <Card title="Inbound conversations" icon="message-square" href="/inbound">
    The same answering behavior, reached from WhatsApp instead of your site.
  </Card>

  <Card title="The agent" icon="user" href="/the-agent">
    What briefs the agent that answers, and when it hands off to a person.
  </Card>

  <Card title="Extraction" icon="table" href="/extraction">
    Why a cold widget conversation has a transcript but no typed fields.
  </Card>

  <Card title="Use MCP" icon="plug" href="/use-mcp">
    Read widget conversations from an AI tool.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.