Deploy Your OmniagentMessaging

Server Events

Server-to-client events published during WebRTC and WebSocket sessions

The server sends events to your client throughout a session. Events notify you of state changes, speech activity, and conversation content as it happens.

Listening for events

WebRTC (Web SDK)

Pass an onData callback when initializing the SDK. Every server event is delivered through this handler:

const instance = await NapsterCompanionApiSdk.init(token, {
  onData: (data) => console.log("Event received:", data),
});

WebSocket

Listen for messages on the WebSocket connection. Each message is a JSON-encoded event:

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log("Event received:", data);
};

Event types

EventDescription
avatar_state_changedThe agent's readiness state changed
talk_state_changedThe agent's speaking state changed
message_receivedA conversation message event with an action field describing the lifecycle stage
ui_updateArbitrary JSON pushed by your own functions server — the platform relays it to the client untouched

avatar_state_changed

Fires when the agent's readiness state changes.

{
  "event": "avatar_state_changed",
  "data": {
    "state": "ready",
    "timestamp": "2026-07-22T08:03:14.309123+00:00",
    "details": { "mode": "audio_only" }
  }
}
StateDescription
preparingThe agent is initializing
readyThe agent is ready to receive input

data.details, when present, describes what became available — for example mode: "audio_only" or mode: "audio_video".

ready can fire more than once per session. On WebRTC with videoPolicy: "deferred", the session becomes ready with audio first and reports ready again when the video avatar comes up — see Video policy. Latch on the first ready instead of re-running session-start logic on every occurrence.


talk_state_changed

Fires when the agent starts or stops speaking.

{
  "event": "talk_state_changed",
  "data": {
    "state": "started"
  }
}
StateDescription
preparingThe agent is preparing to speak
startedThe agent is actively speaking
endedThe agent has finished speaking

message_received

The primary event for tracking the conversation. The conversation fields are nested under data.message:

{
  "event": "message_received",
  "data": {
    "message": {
      "type": "message",
      "role": "user | assistant",
      "action": "string",
      "response_id": "string",
      "item_id": "string",
      "content": "string",
      "content_index": 0,
      "previous_item_id": "string",
      "reason": "string",
      "error": "string",
      "tokens": 0,
      "is_forced": false,
      "timestamp": "2026-03-04T12:00:00Z"
    }
  }
}

The same structure applies on both WebSocket and WebRTC. Over WebRTC, the Web SDK delivers the event to your onData handler exactly as it arrives on the wire.

Not every field is present in every event. The fields included depend on the action.

FieldTypeDescription
type"message" or "session"Frame kind. A "session" frame is a session marker with no role/content — skip it (see below)
role"user" or "assistant"Who the message is from
actionstringThe lifecycle stage of the message
response_idstringGroups related events in the same response
item_idstringUnique identifier for this conversation item
contentstringText content (transcription or response text)
content_indexnumberIndex of the content part within the item
previous_item_idstringID of the preceding item, used to preserve ordering
reasonstringPresent in cancelled events (e.g., "turn_detected")
errorobjectPresent in failed events
tokensnumberToken count for the response (assistant events)
is_forcedbooleantrue when the message was injected by the server rather than produced by user turn-taking
timestampdatetimeWhen the event occurred (included in every message)

The first message_received frame of a session is a session marker: type: "session" with no role, action, or content. Skip any frame where data.message.type === "session" before processing.

The examples below show the contents of the message object — i.e. what you find at data.message.


User message lifecycle

These events track the user's speech input and transcription, all with role: "user".

speech_started

The user began speaking. The item_id identifies the user message item that will be created when speech stops.

{
  "role": "user",
  "action": "speech_started",
  "item_id": "item_APuyFRtVtA9m8o9Qhyj9e"
}

speech_stopped

The user stopped speaking.

{
  "role": "user",
  "action": "speech_stopped",
  "item_id": "item_APuyFRtVtA9m8o9Qhyj9e"
}

created

The user's speech transcription has started. The previous_item_id links this item to the preceding item in the conversation.

{
  "role": "user",
  "action": "created",
  "item_id": "item_APuyFRtVtA9m8o9Qhyj9e",
  "previous_item_id": "item_APuxlnjrohdgargU6tHe9"
}

completed

The user's speech transcription is finalized. The content field contains the full transcription text.

{
  "role": "user",
  "action": "completed",
  "item_id": "item_APuyFRtVtA9m8o9Qhyj9e",
  "content": "Hi there!"
}

This event can arrive after assistant response events have already started. Do not assume user transcription completes before the assistant begins responding.

If content is empty, the user's speech could not be recognized. You may want to display a placeholder like "[inaudible]" in your UI.

failed

The user's speech transcription failed. The error field contains details.

{
  "role": "user",
  "action": "failed",
  "item_id": "item_APuyFRtVtA9m8o9Qhyj9e",
  "error": { }
}

Assistant response lifecycle

These events track the agent's response generation, all with role: "assistant".

created (response-level)

A new response has started generating. The response_id groups all subsequent events for this response.

{
  "role": "assistant",
  "action": "created",
  "response_id": "resp_APuyF0H6ffvKBP0MJE6h9"
}

created (item linked to response)

A response item has been linked to the response.

{
  "role": "assistant",
  "action": "created",
  "response_id": "resp_APuyF0H6ffvKBP0MJE6h9",
  "item_id": "item_APuy7jTGdjgsW0gOGOko2"
}

created (item-level)

The response item has started generating. The previous_item_id preserves ordering in the conversation.

{
  "role": "assistant",
  "action": "created",
  "item_id": "item_APuy7jTGdjgsW0gOGOko2",
  "previous_item_id": "item_APuyFRtVtA9m8o9Qhyj9e"
}

delta

An incremental text chunk of the response. These arrive in rapid succession as the agent generates its reply.

{
  "role": "assistant",
  "action": "delta",
  "response_id": "resp_APuyF0H6ffvKBP0MJE6h9",
  "item_id": "item_APuy7jTGdjgsW0gOGOko2",
  "content": "Hello! How can I"
}

completed

The response item is finished. The content field contains the full response text.

{
  "role": "assistant",
  "action": "completed",
  "response_id": "resp_APuyF0H6ffvKBP0MJE6h9",
  "item_id": "item_APuy7jTGdjgsW0gOGOko2",
  "content": "Hello! How can I assist you today?"
}

interrupted

The user barged in while the agent was responding. This fires on the user side with role: "user".

{
  "role": "user",
  "action": "interrupted",
  "item_id": "item_APuyFRtVtA9m8o9Qhyj9e"
}

cancelled

The response was cancelled. The reason field explains why (e.g., "turn_detected" when the user started speaking).

{
  "role": "assistant",
  "action": "cancelled",
  "response_id": "resp_APuyF0H6ffvKBP0MJE6h9",
  "reason": "turn_detected"
}

failed

The response generation failed. The error field contains details.

{
  "role": "assistant",
  "action": "failed",
  "response_id": "resp_APuyF0H6ffvKBP0MJE6h9",
  "error": { }
}

MCP tool lifecycle

These events report what the agent is doing with an attached MCP server. They have no role — instead, type tells you which stage you are looking at.

Here is the order they arrive in during a session:

  1. The session starts. You receive one mcp_tools message for each attached server, listing the tools that server exposes — or reporting that it could not be reached.
  2. The agent decides to call a tool. You receive an mcp_call message with action: "created", naming the tool and the arguments it will use.
  3. The call finishes. You receive a second mcp_call message, same item_id, with action: "completed" or "failed".

Steps 2 and 3 repeat for every tool call the agent makes.

If the server was registered with requireApproval: "always", one more message arrives between steps 2 and 3: an mcp_approval_request. The call does not run until you answer it, and if you never do, it is dropped after 15 seconds.

mcp_tools event

Tool discovery, sent once per attached server as the session starts.

{
  "type": "mcp_tools",
  "action": "completed",
  "server": "gmail",
  "tool_names": ["search_emails", "get_recent_emails"]
}
FieldTypeDescription
actionstringcompleted or failed — see below
serverstringID of the MCP server this message is about, as you attached it (for example gmail)
tool_namesarray of stringsThe tools this server exposes, for example ["search_emails", "read_email"]. Present on completed; an empty array means the server exposes nothing
errorobjectWhy the server could not be reached. Present on failed

Actions

  • completed — the server was reached and its tools listed. The agent can now call anything in tool_names.
  • failed — the server could not be reached. The agent starts with no tools from it and will not recover during this session. This is your earliest warning of a bad URL, an expired credential or a missing token.

mcp_call event

A tool invocation. You get one message when the call starts and another when it resolves.

{
  "type": "mcp_call",
  "action": "created",
  "item_id": "mcp_a1b2c3",
  "server": "gmail",
  "tool": "get_recent_emails",
  "content": "{\"top_k\": 3}"
}
FieldTypeDescription
actionstringcreated, completed or failed — see below
item_idstringIdentifies this call, prefixed mcp_. The same value appears on the start and the resolution, so use it to pair them
serverstringID of the MCP server providing the tool
toolstringName of the tool being called, for example get_recent_emails
contentstringThe call's arguments as a JSON string on created, and the tool's output on completed
errorobjectDetails of the failure. Present on failed

Actions

  • created — the agent invoked a tool. content holds the arguments it chose. The call has not returned yet, and on an approval-gated server it has not even run.
  • completed — the call returned. content holds the output the agent will speak from.
  • failed — the call errored, typically a rejection from the MCP server itself such as an expired token. The agent says nothing about it.

mcp_approval_request event

A tool call is waiting for your decision. Answer it with send_mcp_approval.

{
  "type": "mcp_approval_request",
  "action": "created",
  "item_id": "mcpr_x9y8z7",
  "server": "gmail",
  "tool": "get_recent_emails",
  "content": "{\"top_k\": 3}"
}
FieldTypeDescription
actionstringcreated, completed or cancelled — see below
item_idstringThe approval request ID, prefixed mcpr_. Present on every action. Send it back as approval_request_id
serverstringID of the MCP server requesting the call. Present on created and cancelled
toolstringName of the tool awaiting approval — show this to the user. Present on created and cancelled
contentstringThe arguments the tool would run with, as a JSON string. Show these too: they are what the user is approving. Present on created
resultstringapproved or rejected. Present on completed
sourcestringHow the decision was made. client when your app answered with send_mcp_approval. Present on completed

Actions

  • created — approval is needed. Present the choice now; the 15-second clock is already running.
  • completed — somebody answered. result tells you which way. Stop showing the prompt.
  • cancelled — nobody answered within 15 seconds, so the call was rejected on your behalf and will not run. Disable the approval controls.

completed and cancelled are mutually exclusive. A request that times out is reported as cancelled only — you never receive a completed for it. So completed always means a real decision was made, and cancelled always means nobody made one.

You have 15 seconds to answer before the request is rejected automatically. Show the choice as soon as created arrives, and disable the controls on cancelled.

ui_update

Unlike the other events, ui_update doesn't originate from the platform — it's sent by your own functions server over the WebSocket that a WebSocket-based explicit tool keeps open during the session. The platform relays it to the client without validating or modifying the payload.

{
  "event": "ui_update",
  "data": { "view": "booking-confirmation", "bookingId": "bk_123" }
}

data is exactly what your server sent — any JSON, any shape. Use it to drive UI from your backend in sync with the conversation: show a confirmation card when a booking tool completes, stream progress for a long-running action, or update page state the agent just changed.

Because the payload is entirely yours, dispatch on a field you define (for example a data.view or data.kind discriminator) rather than assuming a single shape:

onData: (data) => {
  if (data.event === "ui_update") {
    renderFromServer(data.data);
  }
}

Delivery notes:

  • One-way, fire-and-forget: no acknowledgement, and updates are not stored — a client that connects later won't receive past updates. Re-send current state if the UI must survive reconnects.
  • Your server can send it at any time while its WebSocket is connected — it is not tied to a tool call.
  • Requires at least one WebSocket-based explicit tool on the session; HTTP-based tools have no persistent connection to send from.

On this page