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

# Widget events

> Every event the chat widget emits, when it fires, what it carries, and recipes for analytics and your own unread badge.

The widget emits five events to the host page: `ready`, `open`, `close`, `message` and `unread`. Listen with `wiredesk("on", name, fn)` and stop with `wiredesk("off", name, fn)`.

Events are emitted in the visitor's browser, on your page. Nothing is sent to your server. They suit analytics and UI, not record-keeping. For the full conversation history, use [exports](/docs/workspace/exports).

## Listening

```html theme={null}
<script>
  window.wiredesk = window.wiredesk || function () {
    (window.wiredesk.q = window.wiredesk.q || []).push(arguments);
  };

  wiredesk("on", "open", function (event) {
    console.log("chat opened for", event.key);
  });
</script>
<script src="https://wiredesk.ai/widget.js" data-widget="wk_..." async></script>
```

With the queue stub in place, `on` calls made before the script arrives are replayed once it loads, so no event is missed. After the script has loaded, `WireDesk.on(name, fn)` and `WireDesk.off(name, fn)` do the same thing.

* `off` removes the exact function you passed to `on`, so keep a reference to it. Anonymous functions cannot be removed.
* Adding the same function twice means it runs twice.
* Passing something that is not a function to `on` does nothing.

## Reference

| Event     | Fires when                                                            | Payload          |
| --------- | --------------------------------------------------------------------- | ---------------- |
| `ready`   | The conversation panel has loaded inside its iframe.                  | `{ key }`        |
| `open`    | The panel opens, by any route.                                        | `{ key }`        |
| `close`   | The panel closes, by any route.                                       | `{ key }`        |
| `message` | A reply arrives in the panel from the agent or a teammate. See below. | `{ text, role }` |
| `unread`  | A `message` arrives while the panel is closed.                        | `{ count }`      |

`key` is your widget key. `text` is the message text as written, which can include the light Markdown the agent uses in chat (bold, lists, links). `count` is the number of unread messages since the panel was last opened.

### `ready`

Fires when the panel iframe has loaded and can take messages. The iframe is only created when the visitor shows intent: hovering, focusing or touching the launcher, or opening the panel in any way. So `ready` does not fire on a page load where nobody goes near the chat. It fires again whenever the panel reloads, for example after `wiredesk("reset")` on a page where the panel was already loaded.

### `open` and `close`

`open` fires however the panel opens: the launcher, `wiredesk("open")`, a `data-wiredesk-open` element, auto-open, or restoring a panel the visitor left open on the previous page. `close` fires however it closes: the launcher, the panel's own close button, Escape, `wiredesk("close")`, or `wiredesk("hide")` putting away an open panel. `destroy` removes the widget without firing `close`.

Calling `open` on a panel that is already open fires nothing, and the same goes for `close`.

### `message`

`message` fires once for every reply that arrives in the panel:

* the agent's answer to something the visitor sent (`role: "agent"`)
* a reply from a teammate who has taken the conversation over (`role: "human_agent"`)
* a line the agent adds on its own, such as telling the visitor nobody was free after a request for a person went unanswered (`role: "agent"`)

It never fires for the visitor's own messages, the greeting, or earlier messages shown again when the panel reloads a conversation.

| `role`          | Who wrote it                                        |
| --------------- | --------------------------------------------------- |
| `"human_agent"` | A person on your team, replying from the dashboard. |
| `"agent"`       | The AI agent.                                       |

The agent's answer arrives as soon as it is ready. Teammate replies and the agent's own lines are checked for only while the tab is visible and a conversation exists, and the panel catches up when the tab becomes visible again.

### `unread`

Fires with the running count when a `message` arrives while the panel is closed, for example when the visitor closes the panel before the answer comes back. A reply the visitor sees arrive in the open panel is not unread. The launcher shows the same count as a red badge, capped at "9+". Opening the panel resets the count to zero, but no `unread` event fires for the reset. Listen for `open` if you need to clear something.

## Errors in your listeners

Each listener runs in its own `try`/`catch`. If one throws, the widget logs `[wiredesk] a <event> listener threw: <error>` to the console and carries on. The other listeners still run, and the widget keeps working.

## Recipes

### Track opens and closes in your analytics

```js theme={null}
wiredesk("on", "open", function () {
  analytics.track("Support chat opened");
});
wiredesk("on", "close", function () {
  analytics.track("Support chat closed");
});
```

To count replies, split by who wrote them:

```js theme={null}
wiredesk("on", "message", function (event) {
  analytics.track(event.role === "human_agent" ? "Support reply from team" : "Support reply from agent");
});
```

### Show an unread count in your own UI

```html theme={null}
<button data-wiredesk-open>
  Support <span id="support-unread" hidden></span>
</button>

<script>
  var badge = document.getElementById("support-unread");

  wiredesk("on", "unread", function (event) {
    badge.textContent = String(event.count);
    badge.hidden = event.count === 0;
  });

  // Opening the panel clears the count without an unread event.
  wiredesk("on", "open", function () {
    badge.hidden = true;
  });
</script>
```

Once the script has loaded, `WireDesk.unread()` returns the current count if you need to read it rather than wait for an event.

### Open the chat on a particular route

A route such as `/support` or a `?chat=1` link can open the panel on arrival:

```js theme={null}
if (location.pathname === "/support" || new URLSearchParams(location.search).has("chat")) {
  wiredesk("open");
}
```

In a single-page app, call it from your router's navigation hook instead. To open with a question already typed in, pass it along: `wiredesk("open", { message: "Where is my order?" })`. If the panel is already open, `open` does nothing, and the message is not filled in.

### Wait for the panel before doing something

```js theme={null}
wiredesk("on", "ready", function () {
  // the panel iframe is loaded; identify() and prefilled messages are delivered from here on
});
```

You rarely need this. The widget holds anything it sends to the panel until the panel is ready.

## Related

* [JavaScript API](/docs/widget/javascript-api)
* [Widget settings](/docs/developers/widget-settings)
* [Handover](/docs/concepts/handover)
* [Security model](/docs/developers/security-model)
