This page contains two complete, runnable builds. The first build puts a support widget on a page. The widget greets visitors with an AI text agent. It escalates to a human, who sees the full transcript. The second build presents a form and routes the conversation by the visitor’s answers. Both builds use the same three pieces:
  1. A flow that you design once in the console.
  2. A server endpoint that mints a short-lived, namespace-scoped token.
  3. The web component that you put on the page.
The widget opens a single connection (QUIC first, secure WebSocket fallback). It never sees your API key.
Chat is web-component + JavaScript first today. There is no native mobile or polyglot chat SDK. To drive chat from another platform, speak the envelope protocol over a raw MoQT client. The server-side token mint shown here is part of the control-plane API.

How a conversation is wired

Every conversation lives under three MoQT tracks at chat/<org>/<conv>: Messages are JSON envelopes, one per MoQT object:
kind is one of message | typing | form | form_result | event. The full schema is in Widget & API.

Recipe 1 — Support widget with AI-to-human handoff

A visitor opens your site. The widget greets them with an AI text agent. If they ask for a person, the engine offers the conversation to a human agent. The human agent sees the entire transcript. The human joins the same conversation, with the same tracks, so no history is lost.
1

Design the flow in the console

Build a flow named support in the console’s visual flow builder. This is the same step engine that powers voice IVR:
  1. Announce a greeting ("You're chatting with our assistant.").
  2. Queue to skill → AI text agent. Point the step at a text conversation DAG. This is the agent runtime in text mode. It answers on the server track.
  3. Escalation branch. When the visitor’s intent is “talk to a human”, or when the AI calls a handoff tool, queue to skill → human. The visitor keeps chatting on the same conversation. The engine offers it to an available human agent. If the first agent does not pick up, the engine re-offers it (RONA, redirect-on-no-answer).
The human agent’s portal shows the offered conversation with the full transcript already loaded. The agent reads the context before their first reply. One human agent can hold several conversations at the same time (multi-chat concurrency).
2

Mint a token on your server

The widget calls a URL on your server to get a short-lived token. Your server holds the API key. It asks the control-plane API to mint a token scoped to exactly one conversation’s tracks.
The returned token carries an ns claim of chat/acme/<conv>/*. The widget can publish and subscribe only in that one conversation.
3

Drop the web component on the page

Load the widget script and place the element. The tag is your brand name in lowercase plus -chat. The element fetches a token from token-endpoint. It opens the connection and runs the support flow.
index.html
string
required
Your workspace id. It must match the token that the endpoint mints.
string
required
A URL on your server that returns { token, conversationId, relayUrl }. The widget calls it when it first opens.
string
The flow to run. If omitted, the widget uses the workspace’s default chat flow.
'auto' | 'wss'
default:"auto"
auto tries QUIC first and falls back to a secure WebSocket. wss forces the fallback. Use wss behind proxies that block UDP.
string
Header text and launcher placement (bottom-right, bottom-left).
4

Staff the human side

Human agents sign in on the agent portal in the console. Offered conversations appear with the transcript pre-loaded. An agent accepts. Their replies flow onto the agent track. The engine relays that track to server, and the widget renders the replies inline. Set each agent’s concurrent chat capacity so one person can cover several conversations.

What the handoff looks like on the wire

When the flow crosses the escalation branch, the engine emits an event envelope on the server track. The widget can then show a status line. After that, the human’s messages arrive as ordinary message envelopes:
The human sees the transcript: every visitor turn and every AI turn on the conversation. The AI agent’s hidden state does not transfer. That state is its tool-call scratchpad. The human works from the visible conversation, exactly as the visitor sees it.

Recipe 2 — Lead-capture form that routes by result

In this recipe, the flow presents a form. It blocks until a validated result arrives, and then it branches. High-budget leads go straight to a human sales queue. All other visitors continue with the AI text agent. You write no form HTML. The widget renders a form envelope natively and returns a form_result.
1

Design the form and the branch

Build a flow named lead-capture:
  1. Announce a one-line intro.
  2. Present a form and wait for a validated result. The engine sends a form envelope. It does not advance until the widget returns a form_result that passes the field validation you set (required fields, email format).
  3. Branch on the result. Route by a field value. Example: if budget == "$5k+", then queue to skill → human (sales). Otherwise, queue to skill → AI text agent to answer questions and book a demo.
2

Mint the token (flow = lead-capture)

Use the same endpoint as Recipe 1, pointed at the other flow:
3

Embed the widget

landing.html
auto-open="true" opens the panel on load. The form then appears immediately.

The form round-trip

The engine sends a form envelope on the server track. The widget renders the fields. On submit, the widget publishes a form_result on the client track. The flow validates the result and continues.
The branch above tests budget, so the flow queues this visitor to the human sales skill. A $1k–5k visitor continues with the AI text agent instead. The server enforces validation. If email is malformed or budget is missing, the flow presents the form again and does not advance.
Your engine flow collects the form fields. The widget does not post them to a third party. Treat the mint endpoint like any credential surface. Rate-limit it. Set a short ttlSeconds so a leaked token expires quickly.

Drive it from a custom client

Sometimes you cannot use the web component, for example in a native app, a kiosk, or a server-to-server bot. In that case, open the same three tracks over a raw MoQT client and speak the envelope protocol yourself:
  1. Publish message / form_result on client.
  2. Subscribe to server.
  3. Honor typing / event / form.
The wire schema and a minimal client are in Widget & API.

Widget & API

Every widget attribute, the full envelope schema, and a raw-MoQT client.

Chat overview

How chat rides MoQT and shares the flow engine with voice.

Cookbook

Short, single-purpose snippets: embed, form, route, hand off.

Authentication

How short-lived, namespace-scoped tokens are minted and checked.