Chat Embeds & Widget Integration
Updated 2026-07-08
Chat embeds and widget integration
Release: v1.173.0
A Usable Chat embed is an iframe chat experience mounted on a host page. It is configured in Usable Chat, protected by an embed key, and optionally controlled by a TypeScript SDK that communicates with the iframe via postMessage.
Quickstart: basic iframe
- In Usable Chat, open Settings → Embeds.
- Create an embed configuration.
- Create an embed key with allowed domain(s), for example
https://docs.example.com. - Copy the key token and paste the iframe snippet:
<iframe
id="usable-chat"
src="https://YOUR_USABLE_HOST/embed?EMBED_KEY_QUERY"
style="width: 420px; height: 640px; border: 0; border-radius: 12px;"
allow="clipboard-write; file-system"
></iframe>Use exact production hostnames. Wildcard subdomain patterns such as *.example.com are supported.
Quickstart: SDK-controlled widget
The SDK is distributed as a generated file: copy it from the Integrate dialog into your application.
import { createUsableChatEmbed } from './usable-chat-embed-sdk'
const iframe = document.querySelector<HTMLIFrameElement>('#usable-chat')!
const chat = createUsableChatEmbed(iframe, {
iframeOrigin: 'https://YOUR_USABLE_HOST',
onError: (code, message) => console.error('[usable-chat]', code, message),
onConversationChange: (conversationId) => {
if (conversationId) localStorage.setItem('lastUsableConversation', conversationId)
},
})
chat.onReady(async () => {
chat.setAuth(await getFreshUserJwt())
chat.setCssVariables({
'--uc-primary': '#1f7a5b',
'--uc-background': '#ffffff',
})
})Authentication model
Embed key
Every embed uses a key token (uc_...) that enforces config status, key status, allowed domains, and optional configured Usable PAT for anonymous operation.
User bearer token
Pass a signed-in user token with chat.setAuth(token). Implement onTokenRefreshRequest to provide a fresh token when the iframe requests one:
const chat = createUsableChatEmbed(iframe, {
onTokenRefreshRequest: async () => {
const session = await refreshHostSession()
return session?.accessToken ?? null
},
})When an embed chat request returns 401, the iframe asks the parent for a fresh bearer token and retries once if a different token is returned.
PAT-backed anonymous embed
If no bearer JWT is supplied and the embed key has a configured Usable PAT, the embed runs as anonymous/stateless.
Embed configuration
Key configData fields:
{
"ui": {
"inputMode": "contained",
"embedded": true,
"whitelabel": true,
"showConversationPanel": false,
"theme": { "mode": "auto", "primaryColor": "#1f7a5b" },
"ui": {
"placeholder": "Ask the docs…",
"quickReplies": ["How do I get started?"],
"welcomeMessageEnabled": true,
"welcomeMessage": "Hi — ask me about this documentation."
},
"visibility": {
"titleBar": false,
"messageFeedback": true,
"sourceCitations": true,
"toolCallDisplay": true
}
},
"models": { "locked": true, "default": "anthropic/claude-haiku-4.5" },
"features": {
"webSearch": { "enabled": false },
"fileUploads": { "enabled": true },
"visualizations": { "enabled": true },
"fullscreenView": { "enabled": true }
},
"experts": {
"custom": { "enabled": [], "locked": true }
},
"conversation": { "stateless": false },
"defaultContext": { "workspaces": ["workspace-uuid"] },
"feedback": { "mode": "host-event" }
}SDK methods
| Method | Purpose |
|---|---|
onReady(cb) | Run code after the iframe sends READY. |
setAuth(token) | Supply/refresh bearer auth. |
addContext(items) | Add workspace/fragment/collection context. |
registerTools(tools) | Register parent-provided tool schemas. |
setCssVariables(vars) | Runtime theme override (only --uc-* keys). |
submitPrompt(text, { submit }) | Draft or submit text. |
addImage(base64, opts) | Add an image; submit: true sends immediately. |
setVisibility(partial) | Change whitelabel UI visibility toggles. |
| `setTheme('light' | 'dark' |
restoreConversation(id) / newConversation() | Conversation lifecycle. |
toggle(visible) | Show/hide the embed. |
destroy() | Remove listeners and reject pending RPCs. |
Parent tools
Parent tools let the assistant call host-page business functions:
chat.onReady(() => {
chat.registerTools([
{
name: 'lookup_order',
description: 'Look up an order by order number.',
timeoutMs: 20000,
parameters: {
type: 'object',
properties: { orderNumber: { type: 'string' } },
required: ['orderNumber'],
},
},
])
})
const chat = createUsableChatEmbed(iframe, {
onToolCall: async (tool, args) => {
if (tool === 'lookup_order') return await lookupOrder(args)
throw new Error(`Unknown tool: ${tool}`)
},
})The expert/embed must allow parent tools by name.
Startup contract
Use parentContract when startup blocking is needed (optional, off by default):
{
"parentContract": {
"version": 1,
"enforce": true,
"auth": "bearer-with-refresh",
"requiredTools": ["lookupAccount"],
"startupTimeoutMs": 5000
}
}When unsatisfied, the composer is disabled and EMBED_ERROR is emitted with code: "PARENT_CONTRACT_UNSATISFIED".
Stateless parent-side persistence
For embeds where conversation storage lives in the host application:
{ "conversation": { "stateless": true } }The iframe emits CONVERSATION_CREATED and MESSAGE_CREATED events. The parent can restore history with chat.upsertConversationMessages({ conversationId, messages, mode }).
Feedback
Enable with visibility.messageFeedback: true and configure feedback.mode:
host-event: iframe emits{ type: 'EVENT', payload: { event: 'FEEDBACK', data: { rating, messageId, comment } } }.usable-fragment: iframe posts to/api/v2/embed/feedback?token=...and saves a fragment.
File and data access
Opt-in features for trusted host origins:
{
"features": {
"attachments": { "exposeToParent": true },
"workspaceFiles": { "exposeToParent": true },
"dataStaging": { "exposeToParent": true, "acceptExtensions": [".gpx"] }
}
}Then use SDK helpers: chat.attachments.list(), chat.attachments.readBytes(fileId), chat.workspaceFiles.readBytes(fileId), chat.workspaceFiles.putBytes({ fileName, mimeType, base64 }), chat.data.*.
Theming
- Use
ui.whitelabel: trueto strip Usable-branded chrome. - Prefer
theme.mode: 'auto'for device-following light/dark behavior. - Use
setCssVariablesfor runtime changes (only--uc-*keys are accepted). - Embed theme is stored under
localStorage["uc-embed-theme"], isolated from the dashboard.
Troubleshooting
| Issue | Solution |
|---|---|
| Auth/domain error | Confirm the key is active, host origin matches allowedDomains, and setAuth runs after READY. |
| SDK events never fire | Verify iframeOrigin matches the Usable host exactly; confirm the page does not sandbox the iframe. |
| Parent tools are ignored | Register tools after READY; ensure the expert/embed config allows parent tools by name. |
| Feedback does not save | Confirm visibility.messageFeedback: true and that feedback.mode matches the desired destination. |
| Link clicks do nothing | Verify the copied SDK includes OPEN_LINK handling and the URL is http: or https:. |