> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk/vanilla-typescript/events-and-message-handling.md).

# Handle events and messages in TypeScript

Subscribe to connection, state, and message events from the Convai Web SDK client and render the conversation in a framework-free page.

Subscribe to the client's events to keep a framework-free UI in step with the conversation. Use this page when you render state, messages, or live transcription yourself instead of using the chat widget.

### Prerequisites

* A `ConvaiClient` created as shown in [Get started with vanilla TypeScript](/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk/vanilla-typescript.md).

### Subscribe and unsubscribe

`client.on(event, callback)` returns a function that removes the subscription. `client.off(event, callback)` removes it by reference.

```typescript
const unsubscribe = client.on('message', handleMessage);

// Later
unsubscribe();
// or
client.off('message', handleMessage);
```

Subscribe before you call `connect()` so you do not miss the first events of the session.

### Track connection state

`stateChange` fires whenever any field of `ConvaiClientState` changes. `state.agentState` combines the activity flags into one value: `'disconnected'`, `'connected'`, `'listening'`, `'thinking'`, or `'speaking'`.

```typescript
import type { ConvaiClientState } from '@convai/web-sdk/vanilla';

client.on('stateChange', (state: ConvaiClientState) => {
  statusEl.textContent = state.isConnected ? state.agentState : 'disconnected';
});
```

`client.state` returns the same state on demand.

### Wait for the character before sending

`connect` fires when the transport is up. The character can take a moment longer to be ready, and `sendUserTextMessage` does nothing while the transport is not ready. Send the first message on `botReady`, or check `client.isBotReady`.

```typescript
client.on('connect', () => console.log('Transport connected'));

client.on('botReady', () => {
  client.sendUserTextMessage('Hello!');
});

client.on('disconnect', (reason) => {
  console.log('Disconnected with reason', reason);
});
```

### Render the conversation

`message` fires with the newest `ChatMessage`. `messagesChange` fires with the whole array, including updates to a message that is still streaming. For a chat log, re-render from `messagesChange` and filter by `type`:

```typescript
import type { ChatMessage } from '@convai/web-sdk/vanilla';

const SHOWN = new Set(['user-llm-text', 'user-transcription', 'bot-llm-text', 'bot-output']);

client.on('messagesChange', (messages: ChatMessage[]) => {
  chatEl.replaceChildren(
    ...messages
      .filter((m) => SHOWN.has(m.type) && m.content.trim())
      .map((m) => {
        const div = document.createElement('div');
        div.className = m.type.startsWith('user') ? 'user' : 'bot';
        div.textContent = m.content;
        return div;
      }),
  );
});
```

The message types a chat UI usually shows:

| Type                 | Content                                                                                                                                     |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `user-llm-text`      | A text message you sent with `sendUserTextMessage`.                                                                                         |
| `user-transcription` | What the user said, transcribed from speech. Final segments accumulate in one message until the character replies.                          |
| `bot-llm-text`       | The character's reply, streamed as it is generated. `isStreaming` is `true` until it is final.                                              |
| `bot-output`         | The character's reply, aggregated as it is spoken. The SDK adds it to the log only when the reply did not already arrive as `bot-llm-text`. |

Other types carry emotions, actions, idle warnings, and no-response turns. See [Configuration and type reference](/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk/vanilla-typescript/best-practices-and-type-definitions.md#chatmessage) for the full list.

### Show live transcription

`userTranscriptionChange` fires as the user speaks, with the transcription so far. It fires with an empty string when the utterance is final.

```typescript
client.on('userTranscriptionChange', (text: string) => {
  liveTranscriptEl.textContent = text;
});
```

### Handle errors

`error` fires for connection failures and when the character does not report ready in time. Its payload is whatever was thrown, so check the type before reading `message`.

```typescript
client.on('error', (error: unknown) => {
  console.error(error instanceof Error ? error.message : error);
});
```

### Next steps

{% content-ref url="/pages/i6rUAK1CuEI8BPWN76eT" %}
[Web SDK event reference](/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk/event-reference.md)
{% endcontent-ref %}

{% content-ref url="/pages/XcLdxS5Xkd0vnutCN9a5" %}
[Build a custom UI in TypeScript](/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk/vanilla-typescript/building-a-custom-ui-typescript.md)
{% endcontent-ref %}

{% content-ref url="/pages/QOOOI31YjB7Tih8ktqsx" %}
[Handle errors and disconnects](/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk/error-handling.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk/vanilla-typescript/events-and-message-handling.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
