Skip to main content
The iframe runs the same chat as the chat bubble. The chat bubble’s embed script is a wrapper that creates this iframe and posts messages to it. Without the script there is no window.chatbase, so you post the same messages to the iframe yourself. Your page and the chat talk only through postMessage, in both directions. You send messages such as identify or resetChat, and the chat sends events such as assistant-message back. Almost everything in the Features section works in an iframe, and in a mobile app WebView.

When to use the iframe

Use the iframe when:
  • The chat sits inline in your page, such as a help center section, a dashboard panel or a full-page chat.
  • You build your own launcher, layout or open and close behavior.
  • Your platform blocks third-party scripts but allows iframes.
  • You add the chat to a mobile app. See Mobile apps.
Use the chat bubble when you want a floating button with no extra code.

Quick start

1

Copy the iframe code

  1. Go to your Chatbase Dashboard and select your AI agent.
  2. Open Channels, click Manage on the Chat bubble card.
  3. Click Deploy and choose Website iframe.
  4. Copy the code.
2

Add an id

Give the iframe an id so your code can find it. Add allow="microphone" if you use voice or dictation.
If you use a custom domain, the src uses that domain. Every example on this page reads the origin from src, so they work with either.
3

Connect your page

Paste the connector in a script right after the iframe, then log the agent’s replies:
4

Test it

Load the page and send a message.
The agent answers in the iframe and its reply appears in your browser console.

Connect to the iframe

Save this connector as connect-chatbase.js, or paste it into your page. Every example on this page uses it.
connect-chatbase.js
It does four things for you:
  • Posts only to the iframe’s origin, and ignores messages from anywhere else.
  • Holds messages until the chat is ready. Anything posted before iframeReady would otherwise be dropped.
  • Calls your on handlers with the event’s payload as the first argument and the full message as the second.
  • Works with custom domains, because it reads the origin from src.
Using a module bundler? Add export in front of function.

Timing rules

  • Call connectChatbase right after the iframe element, before the iframe loads. If it runs later, it misses iframeReady and send never delivers.
  • Don’t use the iframe’s load event. It can fire before the chat is listening.
  • If the iframe reloads, the chat posts iframeReady again. Put setup such as identify in chat.on("iframeReady", ...) so it runs again too.

Readiness signals

When the iframe restores an existing conversation, conversationReady waits for the chat to have a height, so it never fires under display: none. A new conversation fires regardless. To keep the iframe hidden until ready, use opacity: 0 or visibility: hidden.

Without the connector

The connector is optional. With plain postMessage:
  • Post with the agent origin as targetOrigin, for example https://www.chatbase.co. "*" also works, but never use it for a message that carries a token.
  • Wait for iframeReady before posting.
  • In your message listener, check event.source === iframe.contentWindow and event.origin.
  • Payloads sit under event.data.data.

Common tasks

Each recipe assumes const chat = connectChatbase(iframe) from Connect to the iframe.

Identify a logged-in user

Render a JWT from your server into the page, then send it when the chat is ready. See Identity and user data.
Reference: identify

Reset the conversation on every load

The guard matters. conversationReady fires again after the reset. If you also identify the user, identify must arrive within the 500 ms window, or the reset applies to the anonymous conversation. Reference: resetChat

Reset the conversation from a button

Reference: resetChat

Show the chat only when it’s ready

Reference: Readiness signals

Build your own open and close button

Reference: focusInput, widgetClosed, close

Ask a question for the user

Set options: { hidden: true } to send it without showing it. The agent’s reply still shows. Reference: sendMessage

Give the agent context about the page

The agent reads this context, but anyone using the page can change it. Don’t use it for anything that needs trust. Reference: Metadata without verification

Log the user out

Reference: Log out

Track messages in analytics

Reference: Events you receive

Set the chat language

Applies only when the agent’s language source is set to the website. Reference: URL parameters, setWidgetLanguage

Fit the chat to mobile screens

Reference: windowInnerWidth

Feature support

Everything works in an iframe except the floating launcher, floating initial messages and automatic sizing.

Works in the iframe

Embed script only

These are features of the bubble wrapper, not of the chat. Build them yourself if you need them.

Messages you can send

Every message is an object with a type. The examples use chat.send from the connector.

resetChat

Starts a new conversation. The old one stays in Recent chats.
To reset on every load, see the recipe.

sendMessage

Sends a message as if the user typed it.
string
required
The message text. Blank messages are ignored.
boolean
default:"false"
When true, the message is sent but not shown in the chat. The agent’s reply is shown.
If the agent is busy, the chat holds the message until it is ready. It holds one message. A second message sent while busy replaces the first.

setInitialMessages

Replaces the agent’s initial messages for this session. They show in new conversations, including after resetChat.
string[]
required
Non-empty strings. Text past 1000 characters in total is cut off.
See Custom Initial Messages for use cases.

setOptions and resetOptions

Override display options at runtime. params takes the same fields as setOptions in the embed script.
resetOptions takes the fields to clear, each set to true. An invalid payload is ignored and logs [CHATBASE]: Invalid setOptions payload in the iframe’s console. The iframe posts optionsChanged once the change is on screen.

focusInput

Focuses the message box, unless something else in the iframe already has focus. Send it when you show the iframe.

widgetClosed

Stops voice mode. Send it when you hide the iframe.

windowInnerWidth

Reports your window width. Below 768 pixels the chat shows mobile initial messages. Below 640 pixels it uses its mobile layout. This message has no type.

setWidgetLanguage

Sets the chat language. Applies only when the agent’s language source is set to the website. lang is a language code, or null to fall back to the default.
For the first load, use the hostLang URL parameter instead.

Events you receive

The iframe posts these to your page. With the connector, chat.on(type, (data) => ...) receives the payload as data. type in tool-call and tool-result is the action type. name is the tool name. tool-result can arrive twice for the same call, so dedupe on toolCallId:
Without the connector, the payload is at event.data.data. The embed script and the connector unwrap it for you, so data.content there is event.data.data.content in a raw message listener.
See Event Listeners for use cases.

Identity and user data

The iframe supports the same identity verification as the embed script. Verification happens on the Chatbase server, so it is as secure in an iframe as in the bubble.

Choose when to identify

The chat waits 500 ms after iframeReady for identify. If nothing arrives, the session starts as anonymous. A later identify still works, but earlier messages are not tied to the user, and a reset on load hits the anonymous conversation.
Pick one:
  1. Render the token into the page (recommended). Your server signs the JWT and writes it into the HTML. Send identify on iframeReady. See the PHP example.
  2. Single-page apps: wait to render the iframe. Fetch the token first, then render the iframe. See the React example.
  3. Token arrives late. Send { type: "initConfig", willIdentify: true } on iframeReady, then identify once you have the token. The chat waits with no user until then. initConfig also shows the chat’s close button, which posts close, so handle it.
There’s no event that confirms identification. To check it worked, chat as the user, then open the conversation under Activity in the dashboard and confirm it shows that user.

Methods

JWT

Sign the JWT on your server with your agent’s secret key. See Generating the JWT payload for claims and server code in Node.js, Python, PHP and Ruby.
string
required
The signed JWT. Claims such as email, name, phonenumber, custom_attributes and stripe_accounts update the contact. The agent can’t read them.
object
String values the agent can read. Leave out sensitive data.
In the embed script, every identify field except token becomes a public attribute. In the iframe you put them in publicAttributes yourself.

User hash

Compute the hash on your server: HMAC-SHA256 of the user ID with your secret key, hex encoded.
If the hash is wrong, the user stays anonymous and the iframe logs Invalid user id or user hash. Please check them.

Metadata without verification

Send user_metadata alone to give the agent context.
  • Values are strings. Other values are converted to JSON text.
  • The agent reads up to 1000 characters of metadata.
  • Anyone using the page can change this data. Don’t use it for anything that needs trust, such as account actions.

Log out

Clear the identity when the user logs out, then reset so the next user doesn’t see the conversation.

Conversation history

The chat stores the current conversation in the iframe’s own browser storage. Browsers keep that storage separate for each site that embeds the iframe. Clearing site data or blocking third-party storage starts anonymous users fresh. Identify users to keep their history.

Prefilling forms

Client-side custom actions

Configure the action in the dashboard first. See Client-Side Custom Actions. When the agent calls the action, the iframe posts CHATBOT_TOOL_CALL. Reply with CHATBOT_TOOL_RESULT and the same toolName. result is one of:
  • { status: "success", data }
  • { status: "error", error }
These fields sit at the top level of the message, so read them from the handler’s second argument.
  • The chat waits 60 seconds, then fails the call with Tool call timeout.
  • A result larger than 20 KB, by default, fails with Tool result too large. A malformed one fails with Invalid tool result.
  • Results are matched by toolName. Two calls to the same tool at once can receive each other’s result.
  • Actions set not to wait for a response never post CHATBOT_TOOL_CALL. Listen for tool-call instead.

Client-side custom forms

Configure the form action in the dashboard first. See Client-Side Custom Forms for the schema fields.
The same 60 second timeout and toolName matching apply. An invalid schema fails with Invalid form schema.

Widget functions

Widget functions call your page from inside a widget.
The iframe accepts the result only from your page’s origin. Keep referrerpolicy="no-referrer-when-downgrade" on the iframe so it can detect that origin in every browser.

URL parameters

The iframe reads one URL parameter. The iframe ignores every other parameter, including name, email, user_id, user_metadata and token. URLs end up in browser history, server logs and referrer headers, so identity goes through identify instead. To build the iframe on your server, render the token into the page and post it, as in the PHP example.

Complete examples

Identifies the user, resets on load, logs replies and handles a client-side action. The token request starts while the iframe loads.
If the token request can finish more than 500 ms after iframeReady, render the token into the page instead. See Choose when to identify.

Mobile apps

The simplest way to add your agent to a mobile app is to load the chat in a WebView. Users get the same chat as on your website, including actions, forms, tickets and voice, and there’s no chat UI to build. Use the iOS SDK or Android SDK only when you want your own native chat UI.

How it works

The WebView loads the chat URL directly, with no iframe and no HTML of your own:
Because nothing frames the chat, it posts its events to its own window and reads messages posted to its own window. Your app connects to it in two parts:
  • A bridge script. Inject it before the page loads. It forwards chat events to your native code and sends identify on iframeReady, inside the 500 ms window.
  • Native sends. To send any message from Messages you can send, run window.postMessage(message, location.origin) in the WebView.
Every example below does the same four things: injects the bridge, receives events, opens reply links in the system browser, and lets voice use the microphone.

Examples

WKWebView in a view controller. To use it from SwiftUI, wrap it in a UIViewControllerRepresentable.
ChatbaseViewController.swift
Usage
For voice, add NSMicrophoneUsageDescription to your Info.plist.

Things to know

  • Pass the token when you open the chat. The bridge reads it when the page loads. If the user logs in or out while the chat is open, either reload the WebView with the new token or send identify from native code.
  • History. The WebView keeps its own storage, so the conversation survives app restarts. Identified users also see past conversations under Recent chats. Conversations in the WebView are website chat conversations, so the SDKs’ conversation methods don’t list them.
  • The close event. It fires when the user ends the chat. Dismiss the screen when you receive it.
  • Specific domains doesn’t apply. Nothing frames the chat, so the allowed domains setting has no effect in a WebView.
  • Client-side actions and forms. Your bridge already forwards CHATBOT_TOOL_CALL and GET_FORM_SCHEMA to native code. Reply with CHATBOT_TOOL_RESULT or FORM_SCHEMA_RESULT through a native send.
  • Using your own HTML page instead. You can also load a page of your own that holds the iframe and the connector. Then everything above this section applies as on the web, and your domain must be in the allowed domains list if you turned it on.

Security

Origins

  • Post to the agent origin, never "*", when a message carries a token or user data. The connector does this for you.
  • Check event.source and event.origin in every listener. Any script or frame can post to your window. The connector does this for you.
  • The iframe accepts messages from any page that embeds it. Your allowed domains setting controls which pages can embed it.

Allowed domains

In the dashboard, open Deploy, turn on Specific domains and add every site that embeds the iframe. Browsers then refuse to show the iframe on other sites.

Secrets and data

  • Sign JWTs and user hashes on your server. Never put the secret key in browser or app code.
  • Set exp on every JWT.
  • Public attributes and user_metadata are visible to the agent and editable by the user. Put sensitive data in the JWT.

Iframe attributes

Troubleshooting

See the iframe’s console

Errors from the chat, such as Invalid setOptions payload or Invalid user id or user hash, log inside the iframe. In your browser’s developer tools, open the console’s context menu, which is labeled top by default, and select the Chatbase frame. For a WebView, use Safari’s Develop menu on iOS or chrome://inspect on Android.

Common problems

  • Specific domains is on and your site isn’t in the list. Add the exact domain the page runs on.
  • The visitor is in a country you blocked. The iframe returns 404.
  • The src has the wrong agent ID.
  • The message went out before iframeReady. Use the connector’s send, or send from an iframeReady handler.
  • targetOrigin doesn’t match the iframe’s origin, so the browser dropped it silently. Use new URL(iframe.src).origin.
  • type is misspelled. Types are case sensitive, for example resetChat, not resetchat.
  • setOptions was invalid. Check the iframe’s console.
  • The connector or listener was added after the iframe loaded. Add it in a script right after the iframe element.
  • Your origin check rejects it. With a custom domain, the origin is your custom domain, not https://www.chatbase.co.
  • identify arrived more than 500 ms after iframeReady. See Choose when to identify.
  • The JWT is expired, or signed with the wrong secret or algorithm. Use HS256 and the secret from this agent.
  • The user hash is wrong. The iframe logs Invalid user id or user hash. Please check them.
  • You passed public fields at the top level of params. In the iframe they go in publicAttributes.
Without the connector, the payload sits one level deeper than in the embed script. Read event.data.data.content, not event.data.content.
conversationReady fires again after each reset. Reset only once, guarded by a flag.
  • No listener replied with CHATBOT_TOOL_RESULT within 60 seconds.
  • The reply’s toolName doesn’t match the call.
  • result isn’t { status: "success", data } or { status: "error", error }.
The browser cleared or blocked the iframe’s storage. Anonymous history lives only in that storage. Identify users to keep their history in Recent chats.
You sent initConfig, which shows the close button. Handle the close event and hide the iframe.
  • The bridge was injected after the page loaded. Inject it at document start.
  • On Android, DOCUMENT_START_SCRIPT isn’t supported by the device’s WebView. Update Android System WebView.
  • On Android, domStorageEnabled or javaScriptEnabled is off.

Next steps

Identity Verification

JWT claims and server code

Event Listeners

Use cases for chat events

Client-Side Custom Actions

Configure actions that run in the browser

Chat Interface Control

Runtime options reference