> 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/event-reference.md).

# Event Reference

Reference for subscribing to connection, conversation, speech, action, session, media, and acknowledgment events in the Convai Web SDK.

{% hint style="warning" %}
`modelOutput`, `modelOutputProtocolError`, and v2 tool-call behavior describe an opt-in candidate implementation. They do not confirm production availability or publication in the current `@convai/web-sdk` npm release. Confirm the exports in your installed package.
{% endhint %}

```ts
const unsub = client.on('botReady', () => {
  console.log('Bot is ready');
});

// Later
unsub();
// or
client.off('botReady', handler);
```

***

### Connection events

#### `connect`

Fires when the WebRTC/WebSocket transport connects. The bot may not be ready yet — wait for `botReady` before sending messages.

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

#### `botReady`

Fires when the character has confirmed it is ready to receive messages. This is the correct signal to start interacting.

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

#### `disconnect`

Fires when the session ends. Receives a `DisconnectReason` code.

```ts
client.on('disconnect', (reason) => {
  // reason: number (see DisconnectReason)
  // 1 = CLIENT_INITIATED (intentional), others may warrant reconnection
  if (reason !== 1) {
    client.reconnect();
  }
});
```

#### `stateChange`

Fires whenever any part of `ConvaiClientState` changes. Use this for UI updates.

```ts
client.on('stateChange', (state) => {
  // state.isConnected, state.isConnecting, state.isListening,
  // state.isThinking, state.isSpeaking, state.agentState,
  // state.emotion, state.endUserId, state.metrics, state.disconnectReason
  updateUI(state);
});
```

#### `error`

Fires on connection errors or bot-ready timeout.

```ts
client.on('error', (error: Error) => {
  console.error(error.message);
});
```

***

### Conversation events

#### `conversationStart`

Fires when a new conversation turn begins — either when the user sends a text message or starts speaking.

```ts
client.on('conversationStart', ({ sessionId, userMessage, timestamp }) => {
  // sessionId: incrementing turn counter
  // userMessage: text sent, or "[voice]" for voice turns
  // timestamp: Date.now() value
  console.log(`Turn ${sessionId} started`);
});
```

#### `turnEnd`

Fires when the bot finishes speaking for a turn.

```ts
client.on('turnEnd', ({ sessionId, duration, timestamp }) => {
  // duration: seconds the bot spoke
  console.log(`Turn ${sessionId} lasted ${duration}s`);
});
```

#### `message`

Fires for each new `ChatMessage` added to the conversation. The full history is also available in `client.chatMessages`.

```ts
client.on('message', (msg) => {
  // msg.type, msg.content, msg.id, msg.timestamp, msg.isStreaming
  if (msg.type === 'bot-output') {
    display(msg.content);
  }
});
```

#### `messagesChange`

Fires whenever the message array changes (includes message updates mid-stream).

```ts
client.on('messagesChange', (messages) => {
  renderHistory(messages);
});
```

#### `userTranscriptionChange`

Fires repeatedly as the user speaks, providing live speech-to-text.

```ts
client.on('userTranscriptionChange', (text) => {
  showLiveTranscript(text);
});
```

***

### Speaking events

#### `speakingChange`

Fires when the bot starts or stops speaking.

```ts
client.on('speakingChange', (isSpeaking) => {
  if (isSpeaking) {
    startLipsyncAnimation();
  } else {
    stopLipsyncAnimation();
  }
});
```

#### `botOutput`

Fires for each aggregated output chunk from the bot. Includes both spoken and unspoken text.

```ts
client.on('botOutput', ({ text, spoken, aggregatedBy }) => {
  // spoken: true when the TTS engine will read this text aloud
  // aggregatedBy: "sentence" or "word" indicating chunk granularity
  if (spoken) {
    addToSubtitles(text);
  }
});
```

#### `botTtsStarted`

Fires when the TTS engine starts producing audio.

```ts
client.on('botTtsStarted', () => {
  showSpeakingIndicator();
});
```

#### `botTtsStopped`

Fires when the TTS engine finishes.

```ts
client.on('botTtsStopped', () => {
  hideSpeakingIndicator();
});
```

#### `botTtsText`

Fires word-by-word as the bot speaks, synchronized with TTS audio.

```ts
client.on('botTtsText', ({ text }) => {
  highlightWord(text); // karaoke-style word highlighting
});
```

***

### Microphone events

#### `userMuteStarted`

Fires when the server-side mutes the user's microphone (e.g., when the bot starts speaking to prevent echo).

```ts
client.on('userMuteStarted', () => {
  showMicMutedIndicator();
});
```

#### `userMuteStopped`

Fires when the server un-mutes the user's microphone.

```ts
client.on('userMuteStopped', () => {
  hideMicMutedIndicator();
});
```

***

### Blendshape / lipsync events

These require `enableLipsync: true` in config.

#### `blendshapes`

Fires for each incoming blendshape chunk (10 frames by default). Use `client.blendshapeQueue` instead of handling raw chunks directly.

```ts
client.on('blendshapes', (data) => {
  // Low-level: raw chunk data before queue processing
});
```

#### `blendshapeStatsReceived`

Fires when the server sends end-of-turn blendshape statistics. Signals that no more frames are coming for this turn.

```ts
client.on('blendshapeStatsReceived', (stats) => {
  // stats.total_blendshapes, stats.total_audio_duration_ms
});
```

***

### Action events

`actionResponse` requires semantic affordances or v2 tool declarations. `modelOutput` requires `modelOutputVersion: 2` and can carry text even when no Actions contract is active.

#### `actionResponse`

Fires on the legacy compatibility event surface. Under action protocol v1, the ordered array contains semantic actions. Under action protocol v2, it can also contain correlated tool calls. The SDK does not execute or schedule them.

```ts
import type { ActionResponseEvent } from '@convai/web-sdk/core';

client.on('actionResponse', ({ actions }: ActionResponseEvent) => {
  for (const action of actions) {
    handleAuthorizedAction(action);
  }
});
```

When `modelOutputVersion: 2` is selected, the SDK suppresses `actionResponse` to prevent duplicate handling. Use `modelOutput` instead.

**`modelOutput`**

Fires for each valid canonical v2 output envelope when `modelOutputVersion: 2` was selected. The SDK preserves item order and drops a repeated `output_id`; it does not execute items or guarantee exactly-once tool execution.

```ts
import type { ModelOutputMessage } from '@convai/web-sdk/core';

client.on('modelOutput', (output: ModelOutputMessage) => {
  for (const item of output.items) {
    if (item.type === 'message') renderMessage(item);
    if (item.type === 'tool_call') handleAuthorizedToolCall(item);
  }
});
```

Use validated `items` for rendering and execution. Treat `raw` as diagnostics only. Distinct envelopes can share a `logical_turn_id`, and `final: true` finalizes one envelope rather than the entire logical turn.

**`modelOutputProtocolError`**

Fires when a selected v2 `model-output` envelope is malformed or contains an unsupported item. The parser rejects the whole envelope.

```ts
import type { ModelOutputProtocolError } from '@convai/web-sdk/core';

client.on(
  'modelOutputProtocolError',
  ({ code, message }: ModelOutputProtocolError) => {
    console.error(code, message);
  },
);
```

The `code` is currently `"invalid_model_output"`. See [Actions](/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk/actions.md) for configuration, tool results, and limits.

***

### Server response events

#### `serverResponse`

Fires as an acknowledgment for every message you send to the server.

```ts
client.on('serverResponse', (response) => {
  // response.event_type: the message type you sent
  // response.status: "success" | "error" | "processing" | "pending"
  // response.message: human-readable result
  // response.extras: event-specific data (e.g., token counts for context-update)
  if (response.status === 'error') {
    console.error(`Server error on ${response.event_type}:`, response.message);
  }
});
```

For `action-result`, `extras.tool_call_id` correlates the acknowledgment and `extras.idempotent` reports whether an identical result was already accepted. A `void` return from `sendActionResult()` is not an acknowledgment.

#### `interactionCreated`

Fires early in the session lifecycle — before `botReady` — when the server assigns a unique interaction ID. This is the first message that carries both `interactionId` and `characterSessionId`, making it the right place to capture identifiers for analytics, logging, or session resumption.

```ts
client.on('interactionCreated', ({ interactionId, characterSessionId }) => {
  console.log('Interaction ID:', interactionId);       // e.g. "int_abc123def456"
  console.log('Character session:', characterSessionId); // e.g. "cs_xyz789"

  // Store for analytics / logging
  analytics.track('conversation_started', {
    interactionId,
    characterSessionId,
    timestamp: Date.now(),
  });
});
```

| Field                | Type     | Description                                                                            |
| -------------------- | -------- | -------------------------------------------------------------------------------------- |
| `interactionId`      | `string` | Unique identifier for this interaction. Use for analytics or log correlation.          |
| `characterSessionId` | `string` | Character session identifier. Same value as `client.characterSessionId` after connect. |

`interactionCreated` fires once per `connect()` call. If you reconnect, a new `interactionId` is issued.

***

### Session events

#### `idleWarning`

Fires before the server disconnects an idle session.

```ts
client.on('idleWarning', ({ remainingSeconds }) => {
  if (remainingSeconds !== null) {
    showWarning(`Session will close in ${remainingSeconds}s`);
  }
  // Reset the timer on any user activity
  client.resetIdleTimer();
});
```

#### `llmNoResponse`

Fires when the LLM deliberately chose not to respond (e.g., because the input didn't warrant a reply).

```ts
client.on('llmNoResponse', () => {
  // No response will arrive for this turn — update UI accordingly
  hideThinkingIndicator();
});
```

***

#### `metrics`

Fires with performance data after each turn.

```ts
client.on('metrics', (data) => {
  // Raw metrics from the server (latency, token counts, etc.)
  console.log('Turn metrics:', data);
});
```

***

### Audio track (WebSocket transport)

#### `botAudioTrack`

Fires when a new audio track is available from the bot (WebSocket transport only). Attach to an `<audio>` element to play.

```ts
client.on('botAudioTrack', (track: MediaStreamTrack) => {
  const audio = document.querySelector<HTMLAudioElement>('#bot-audio')!;
  audio.srcObject = new MediaStream([track]);
});
```

***

### Event quick-reference

| Event                      | Payload                                 | When                            |
| -------------------------- | --------------------------------------- | ------------------------------- |
| `connect`                  | —                                       | Transport connected             |
| `botReady`                 | —                                       | Bot confirmed ready             |
| `disconnect`               | `DisconnectReason`                      | Session ended                   |
| `stateChange`              | `ConvaiClientState`                     | Any state change                |
| `error`                    | `Error`                                 | Connection or timeout error     |
| `conversationStart`        | `{ sessionId, userMessage, timestamp }` | New turn begins                 |
| `turnEnd`                  | `{ sessionId, duration, timestamp }`    | Bot finishes speaking           |
| `message`                  | `ChatMessage`                           | New message                     |
| `messagesChange`           | `ChatMessage[]`                         | History updated                 |
| `userTranscriptionChange`  | `string`                                | Live STT update                 |
| `speakingChange`           | `boolean`                               | Bot speaking state              |
| `botOutput`                | `{ text, spoken, aggregatedBy }`        | Aggregated bot chunk            |
| `botTtsStarted`            | —                                       | TTS begins                      |
| `botTtsStopped`            | —                                       | TTS ends                        |
| `botTtsText`               | `{ text }`                              | Word-by-word TTS                |
| `userMuteStarted`          | —                                       | Server muted user mic           |
| `userMuteStopped`          | —                                       | Server un-muted user mic        |
| `blendshapes`              | raw data                                | Blendshape chunk                |
| `blendshapeStatsReceived`  | stats                                   | Turn end stats                  |
| `actionResponse`           | `{ actions }`                           | Compatibility action projection |
| `modelOutput`              | `ModelOutputMessage`                    | Valid canonical v2 envelope     |
| `modelOutputProtocolError` | `ModelOutputProtocolError`              | Invalid v2 envelope             |
| `serverResponse`           | `ServerResponse`                        | Server acknowledgment           |
| `interactionCreated`       | `{ interactionId, characterSessionId }` | Session ID assigned             |
| `idleWarning`              | `{ remainingSeconds }`                  | Idle timeout warning            |
| `llmNoResponse`            | —                                       | LLM chose not to respond        |
| `metrics`                  | data                                    | Turn performance data           |
| `botAudioTrack`            | `MediaStreamTrack`                      | New audio track (WS transport)  |


---

# 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 dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.convai.com/api-docs/plugins-and-integrations/web-plugins/convai-web-sdk/event-reference.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
