EmbedChat Component
Chat for a page shown inside another page (an iframe), signed in with an identity token the host page's server signs. It runs the postMessage handshake, trades the token for an in-memory session, keeps that session alive, reports events to the host page and lets the agent call the host page's tools.
New in 0.4.0
EmbedChat and its helpers are new. The protocol, the events, the origin checks and a complete host page are in Embedding the Chat; this page is the component reference.

Import
import { EmbedChat, type EmbedChatProps, type EmbedClientToolDeclaration, type EmbedSessionApi } from "@cognipeer/chat-ui";
import "@cognipeer/chat-ui/styles.css";2
Usage
<EmbedChat
baseUrl="/api/portal/support"
allowedOrigins={["https://app.example.com"]}
clientTools={[{ name: "open_ticket", inputSchema: { type: "object", required: ["id"], properties: { id: { type: "number" } } }, timeoutSeconds: 30 }]}
eventContent="none"
showHistory={false}
theme="dark"
/>2
3
4
5
6
7
8
Props
EmbedChatProps is ChatProps without transport, authorization, headers, getHeaders, fetch, onClientToolCall, controllerRef and getMessageMetadata (the embed owns them), plus:
| Prop | Type | Default | Description |
|---|---|---|---|
baseUrl | string | required | The portal's chat API, for example /api/portal/support |
sessionUrl | string | <baseUrl>/embed/session | Where the host's token is exchanged (POST) and the session revoked (DELETE) |
allowedOrigins | string[] | required | Origins that may embed the page: exact matches, no wildcards |
hostOrigin | string | the origin query parameter | The embedding page's origin as it declared itself |
clientTools | EmbedClientToolDeclaration[] | [] | { name, description?, inputSchema?, timeoutSeconds? }: the only host-page tools that exist for the agent |
eventContent | "none" | "text" | "none" | Whether message events carry the message text (4000 characters at most) |
wrapChat | (chat, session: EmbedSessionApi) => ReactNode | - | Draw something around the chat once the person is signed in (a terms gate, a banner) |
authTimeoutMs | number | 10000 | Wait for the host's first token before the screen says so and auth:required is sent |
readyIntervalMs | number | 500 | How often cp:ready is repeated |
fetch, window | globals | For tests and special setups |
EmbedSessionApi is { getHeaders(): Record<string, string>; fetch: typeof fetch; user?: { name?; email? } }: the headers (a bearer token) and a fetch that renews the session once after a 401, for requests made by the pieces you draw around the chat (for example the terms acceptance).
EmbedChat also forwards your own onMessageSent, onMessageReceived, onConversationCreated, onActionRequired, onActionResolved, onBudgetReached and onError after reporting the matching event to the host.
What it renders
Until the first session exists it renders a status screen instead of the chat: connecting, waiting for the host, signing in, refused (origin or framing), sign-in failed, session expired or signed out. All strings are in the chat.embed.* group.
Building your own bridge
The pieces EmbedChat is made of are exported for hosts that need something different:
| Export | Purpose |
|---|---|
EmbedBridge | The iframe's end of the conversation with the host: start() (returns a refusal reason, or starts), stop(), requestToken(reason), emit(event, data), callTool(call, timeoutMs). Posts only to window.parent with the declared origin as target, and listens only to the parent window at that origin |
isAllowedOrigin(origin, allowed), normalizeOrigin(value) | Exact origin matching |
parseHostMessage(data) | Validates a message from the host (undefined for anything that is not ours) |
exchangeToken({ url, token, fetch?, signal? }) | POST { token } with no cookies; resolves an EmbedSession or rejects with an ExchangeError (code, status?, reference?) |
parseExchangeResponse(body, now), renewalLeadMs(session) | Reading the answer; how long before its end a session is renewed |
EMBED_PROTOCOL_VERSION, EMBED_EVENTS, MAX_TOKEN_CHARS, MAX_CONTEXT_BYTES, MAX_TOOL_OUTPUT_BYTES | Protocol constants |
BridgeRefusal is "not_framed" | "origin_not_allowed" | "ancestor_mismatch".
Related
- Embedding the Chat
- Portal Components (
TermsGate) - Embedding & limits (the server side)

