# Installing (/docs/agents/chat-widget/installing)

> For the complete documentation index, see [llms.txt](https://docs.tandemcx.ai/llms.txt). Any page on this site can be fetched as Markdown by appending `.md` to its URL.

## 1. Create the widget

In Tandem, go to **Web Chat → Widgets** and choose **Add Widget**. You need a
name — which visitors see as the bot name in the chat header — and the agent it
should talk to. Everything else is set on the page you land in.

## 2. Add your allowed origins

Under **Allowed Origins**, add every website origin the widget should work on:

```
https://www.example.com
https://shop.example.com
```

An origin is the scheme, host, and optional port — **no path and no trailing
slash**. There is no wildcard: if you serve the chat from three subdomains, list
three origins.

<Callout type="warn">
    A widget with no allowed origins does not load anywhere, including the site you are testing
    from. This is deliberate — see [Why the origin list matters](#why-the-origin-list-matters).
</Callout>

While you are developing, add your local origin too. `http://` is accepted only
for `localhost`, `127.0.0.1`, and `[::1]`; every other origin must be `https://`.

```
http://localhost:5173
```

## 3. Paste the snippet

Copy the snippet from the **Install** section and paste it into your site, just
before the closing `</body>` tag on every page that should show the chat:

```html
<script
    src="https://chat.tandemcx.ai/v1/spark-chat.js"
    data-spark-chat-key="spk_YOUR_PUBLIC_KEY"
    async
></script>
```

A snippet copied from the dashboard before it moved to `app.tandemcx.ai` loads the
script from `chat.spark.sharpencx.com` instead. Both hosts serve the same script, and
either works.

That is the whole integration. The script adds one element to your page and
renders inside a shadow root, so your site's CSS cannot affect the chat and the
chat's CSS cannot affect your site.

### Optional attributes

The launcher's corner is **not** one of these. It is a widget setting, so it moves
from the Tandem dashboard without editing the snippet on every page. See
[Position](/docs/agents/chat-widget#the-launcher).

| Attribute                 | Default      | What it does                                                                                                                                       |
| ------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data-spark-chat-z-index` | `2147483000` | Raise or lower if the launcher collides with another fixed element.                                                                                |
| `data-spark-chat-auto`    | `true`       | Set to `false` to stop the widget mounting itself, and call `SparkChat.mount({ publicKey: 'spk_…' })` yourself — for example after cookie consent. |

### Controlling it from your own code

`window.SparkChat` is available as soon as the script has run:

```js
SparkChat.open();
SparkChat.close();
SparkChat.toggle();
SparkChat.reset(); // discard the conversation and start over
SparkChat.destroy(); // remove the widget from the page
```

## Why the origin list matters

The public key in your snippet is **not a secret**. Anyone who views your page
source can read it, and there is no way around that — the snippet has to work in
a browser with no login.

So the key is not what authorizes a request. The allowed-origins list is. Your
visitor's browser sends an `Origin` header that page JavaScript cannot forge, and
Tandem checks it against your list on every request. Someone who copies your
snippet onto their own site gets nothing.

Keep the list to the sites that should have the chat, and it does its job.

## What the widget does to your page

- Adds one `<spark-chat>` element to `<body>`, rendered in a shadow root.
- While the chat is open **on a phone-sized screen**, sets `overflow: hidden` on
  `<html>` so the page behind the full-screen panel does not scroll, and restores
  your previous value exactly when it closes.
- Stores two small values in the visitor's browser, and **no message text**:
  a visitor id and the id of the conversation they were in, in `localStorage`
  under one key. The messages themselves stay on our servers and are fetched when
  the panel opens. **No cookie is set** — the widget is on your domain calling
  ours, so a cookie would be a third-party cookie, which several browsers block.
- Also uses one `sessionStorage` key, to remember that this tab has already been
  greeted.
- Defines `window.SparkChat`.

It sets no other global listeners, and never touches your page's history, title,
or focus outside its own panel.

## Content Security Policy

If your site sends a CSP, the widget needs:

```
script-src  https://chat.spark.sharpencx.com https://chat.tandemcx.ai
connect-src https://api.spark.sharpencx.com https://api.tandemcx.ai
```

List both `script-src` hosts so the policy holds whichever snippet your site uses. The widget
calls `api.spark.sharpencx.com` today. List `api.tandemcx.ai` in `connect-src` as well, so
your policy keeps working when the widget moves to that host.

Add `img-src` for whatever host your avatar image is on. The widget loads no
fonts, no stylesheets, and no other scripts.
