If you’re using the embed widget npm package, you don’t need this page — the widget handles all postMessage communication internally. This protocol is for developers who embed the agent using a direct iframe and want programmatic control.
The Thesys agent iframe communicates with the parent window using the browser’s postMessage API. Messages are plain objects with a type field that identifies the action.
Parent → Iframe
Send messages to the agent by calling postMessage on the iframe’s contentWindow:
const iframe = document.getElementById('thesys-agent');
iframe.contentWindow.postMessage(
{ type: 'THESYS_SEND_MESSAGE', message: 'Hello!', newThread: false },
new URL(iframe.src).origin
);
// Reset the current thread (start a new chat)
iframe.contentWindow.postMessage(
{ type: 'THESYS_RESET_THREAD' },
new URL(iframe.src).origin
);
// Toggle the conversation history sidebar
iframe.contentWindow.postMessage(
{ type: 'THESYS_TOGGLE_SIDEBAR' },
new URL(iframe.src).origin
);
Always use a specific targetOrigin (e.g. new URL(iframe.src).origin) instead of "*" to prevent leaking data if the iframe navigates to an unexpected URL.
Message Types
| Type | Payload | Description |
|---|
THESYS_SEND_MESSAGE | { message: string, newThread: boolean } | Send a user message to the agent |
THESYS_SET_INPUT | { message: string, newThread: boolean } | Prefill the chat input without sending |
THESYS_IDENTITY_TOKEN_REFRESHED | { identityToken: string } | Provide a fresh identity token after a refresh request |
THESYS_RESET_THREAD | (none) | Start a new conversation thread, clearing the current chat |
THESYS_TOGGLE_SIDEBAR | (none) | Open or close the conversation history sidebar |
Iframe → Parent
The agent sends messages to the parent window. Listen for them with addEventListener:
const expectedOrigin = new URL(iframe.src).origin;
window.addEventListener('message', (event) => {
if (event.origin !== expectedOrigin) return;
if (!event.data || typeof event.data.type !== 'string') return;
switch (event.data.type) {
case 'THESYS_APP_READY':
// Agent has finished loading and is ready
break;
case 'THESYS_THREAD_CHANGED':
// Active thread changed — event.data.threadId
break;
case 'THESYS_NEW_THREAD':
// User started a new chat
break;
case 'THESYS_USER_MESSAGE_SENT':
// User sent a message — event.data.message, event.data.threadId
break;
case 'THESYS_GENERATION_STARTED':
// Agent started generating — event.data.threadId, event.data.messageId
break;
case 'THESYS_GENERATION_ENDED':
// Agent finished generating — event.data.threadId, event.data.messageId
// event.data.message holds the full persisted assistant message,
// only when this origin is on the allowedParentOrigins list.
break;
case 'THESYS_TOOL_EXECUTION_STARTED':
// Tool started — event.data.toolName, event.data.threadId
break;
case 'THESYS_TOOL_EXECUTION_ENDED':
// Tool finished — event.data.toolName, event.data.threadId, event.data.error
break;
case 'THESYS_AGENT_ERROR':
// Error occurred — event.data.code, event.data.message
break;
case 'THESYS_WIDGET_CLOSE':
// Agent requested to close
break;
case 'THESYS_WIDGET_OPEN':
// Agent requested to open
break;
case 'THESYS_WIDGET_TOGGLE':
// Agent requested to toggle open/close
break;
case 'THESYS_IDENTITY_TOKEN_REFRESH_NEEDED':
// Identity token expired — fetch a new one and send it back
break;
case 'THESYS_IDENTITY_TOKEN_ERROR':
// Identity token error — event.data.code, event.data.message
break;
}
});
Always validate event.origin against the expected iframe origin to prevent unauthorized windows from triggering actions.
Message Types
| Type | Payload | Description |
|---|
THESYS_APP_READY | (none) | The agent has finished loading and is ready for interaction |
THESYS_WIDGET_CLOSE | (none) | The agent is requesting to be closed |
THESYS_WIDGET_OPEN | (none) | The agent is requesting to be opened |
THESYS_WIDGET_TOGGLE | (none) | The agent is requesting to toggle its visibility |
THESYS_THREAD_CHANGED | { threadId: string } | The active conversation thread changed |
THESYS_NEW_THREAD | (none) | The user started a new chat (before the thread is created) |
THESYS_USER_MESSAGE_SENT | { message: string, threadId: string } | The user sent a message |
THESYS_GENERATION_STARTED | { threadId: string, messageId: string } | The agent started generating a response |
THESYS_GENERATION_ENDED | { threadId: string, messageId: string, message?: AssistantMessage } | The agent finished generating a response. message is the full persisted assistant item, present only for allowlisted origins (see Allowed Parent Origins). |
THESYS_TOOL_EXECUTION_STARTED | { toolName: string, threadId: string } | A tool started executing |
THESYS_TOOL_EXECUTION_ENDED | { toolName: string, threadId: string, error?: string } | A tool finished executing |
THESYS_AGENT_ERROR | { code: string, message: string } | An error occurred during processing |
THESYS_IDENTITY_TOKEN_REFRESH_NEEDED | (none) | The identity token has expired; parent should provide a new one |
THESYS_IDENTITY_TOKEN_ERROR | { code: string, message: string } | An identity token error occurred |
If you’re using the embed widget npm package, you can use the higher-level Events API instead of listening for raw postMessage events.
Allowed Parent Origins
For security, the agent splits its outgoing messages into two classes:
- Protocol events — the handshake (
THESYS_APP_READY), widget controls (THESYS_WIDGET_OPEN/CLOSE/TOGGLE), and THESYS_IDENTITY_TOKEN_REFRESH_NEEDED. These are required for the embed to function and are always sent to '*' with their full payload.
- Data events — anything that carries information about the user’s conversation:
THESYS_THREAD_CHANGED, THESYS_NEW_THREAD, THESYS_USER_MESSAGE_SENT, THESYS_GENERATION_STARTED, THESYS_GENERATION_ENDED, THESYS_AGENT_ERROR, THESYS_TOOL_EXECUTION_STARTED/ENDED, and THESYS_IDENTITY_TOKEN_ERROR.
Data events are only delivered with their full payload to parent origins on the playground’s Allowed Parent Origins allowlist. When the allowlist is empty, the event still fires, but the payload is stripped to just { type } so non-opted-in embedders cannot read private conversation data.
// Allowlist empty (default):
// Parent receives: { type: 'THESYS_GENERATION_ENDED' }
//
// Allowlist contains the parent's origin:
// Parent receives: {
// type: 'THESYS_GENERATION_ENDED',
// threadId: '…',
// messageId: '…',
// message: { id, role: 'assistant', content: [...], ... }
// }
Configuring the allowlist
- Open your playground in the Thesys Console.
- Click Deploy, expand the Embed on your website section.
- Under Allowed parent origins, add each origin (e.g.
https://app.example.com, http://localhost:3000) that should receive full event payloads.
- Republish the playground — the new allowlist applies to all subsequent loads of the iframe.
Origins must be exact scheme://host[:port] matches. No paths, no trailing slashes, no wildcards.
Identity Token Refresh
When a BYOI identity token expires during an active session, the agent sends THESYS_IDENTITY_TOKEN_REFRESH_NEEDED to the parent. The parent should fetch a new token from its backend and reply with THESYS_IDENTITY_TOKEN_REFRESHED:
const iframe = document.getElementById('thesys-agent');
const agentOrigin = new URL(iframe.src).origin;
window.addEventListener('message', async (event) => {
if (event.origin !== agentOrigin) return;
if (event.data?.type === 'THESYS_IDENTITY_TOKEN_REFRESH_NEEDED') {
try {
const res = await fetch('/api/thesys-token');
const { token } = await res.json();
iframe.contentWindow.postMessage(
{ type: 'THESYS_IDENTITY_TOKEN_REFRESHED', identityToken: token },
agentOrigin
);
} catch (error) {
console.error('Failed to refresh identity token:', error);
}
}
});
If the parent doesn’t respond within 10 seconds, the agent shows an error modal to the user.
URL Parameters
When embedding via a direct iframe, you can configure the agent through URL query parameters:
| Parameter | Value | Description |
|---|
IDENTITY_TOKEN | JWT string | Sets the initial identity token for BYOI |
HIDE_LOGIN | "true" | Hides the Thesys login UI |
appRenderContext | TRAY_EMBED_WIDGET | FULLSCREEN_EMBED_WIDGET | CHATBAR_EMBED_WIDGET | Tells the agent which embed layout context it’s running in |
<iframe
id="thesys-agent"
src="https://console.thesys.dev/app/your-slug?IDENTITY_TOKEN=eyJ...&HIDE_LOGIN=true"
style="width: 100%; height: 600px; border: none;"
></iframe>