Skip to main content

Embedded chat widget

This page shows you how to add a floating chat launcher — like an Intercom bubble — to your own website, wired to one of your agents. Visitors chat anonymously; you control the look and the list of sites allowed to embed it.

How it works

The widget is a single script tag. It renders a launcher button in the corner of your page. Every conversation flows through a public path that carries no sign-in cookie at all, identified only by an embed key.

An embed key is a public identifier shaped `emb_` plus exactly 24 lowercase letters and digits. Think of it as an app id, not a secret: it grants access to chat with one agent, and nothing else.

The widget makes exactly two kinds of network calls: one to fetch its configuration (title, colors, greeting) and one per message. No sign-in cookie or token ever travels on this path. The key in the URL is the only identity, and the server checks the visiting page's origin — the scheme, host, and port it was served from — against your allowlist.

Enable the widget

Turning the widget on for the first time mints the agent's embed key. Set the allowed origins in the same step — they are the widget's real access control.

platformctl agents embed enable my-agent \
--allow-origin https://yoursite.com \
--allow-origin https://www.yoursite.com \
--title "Ask us anything" \
--greeting "Hi! How can I help?"

You should see the saved configuration, embed_key included:

accent_color -
allowed_origins https://yoursite.com, https://www.yoursite.com
embed_key emb_xxxxxxxxxxxxxxxxxxxxxxxx
enabled true
greeting Hi! How can I help?
launcher_text -
position right
subtitle -
title Ask us anything
updated_at 2026-08-15T10:22:04Z

A - means the field is unset — the widget falls back to its built-in default.

--allow-origin is repeatable. --subtitle, --accent-color, --launcher-text, and --position left|right complete the appearance set.

Run it again later to change one thing: the command reads the current configuration first and only changes the flags you actually passed, so embed enable on its own turns the widget back on without blanking a title set from the console.

Write each origin as scheme://host, with an optional port — for example https://yoursite.com. The most common mistake is pasting a full page URL. An origin with a path on the end is rejected with "<value>" is not a valid origin: it must look like https://example.com (scheme + host, no path), and the console names the offending line before the server ever sees it.

An empty allowlist means every site

If you leave the allowed-origins list empty, any website can embed your widget and chat with your agent. List your real sites before you ship the snippet.

Paste the snippet

The snippet is the same however you enabled the widget — the Embed tab builds it for you, and from the CLI or the API you drop the embed_key into it yourself. Paste it just before </body> on your site:

<script async src="https://console.codyhill.dev/embed/widget.js"
data-embed-key="emb_xxxxxxxxxxxxxxxxxxxxxxxx"></script>

That's the whole integration. The widget pulls in no other libraries, and it renders inside a Shadow DOM — a sealed-off corner of the page with its own styles. Your site's CSS cannot break the widget, and the widget cannot break your site. Pasting the snippet twice still renders only one widget.

What visitors experience

  • A floating launcher opens a chat panel. The greeting shows first (default: Hi! How can I help?).
  • Each visitor gets a lasting anonymous identity. The widget stores a session id in a cookie, and the transcript in the browser's local storage — a small store the browser keeps per site. The conversation therefore resumes across page loads. The transcript keeps the last 50 messages.
  • Each message waits up to 70 seconds for a reply. That is deliberately generous: an idle agent runs zero copies of itself, so the first message after a quiet spell has to wait for one to start up. That wait is called a cold start.

You can watch these conversations from the agent's Users & Sessions view in the console.

Customize per page

You can override the look on one page without changing what you saved in the console. Put the overrides on the script tag. When the same setting exists in two places, the server-saved value wins over the page override, and the page override wins over the built-in default.

AttributeOverrides
data-titlePanel title (default Chat)
data-subtitlePanel subtitle
data-greetingFirst message shown
data-accent-colorBrand color (default #f9743a)
data-launcher-textText next to the launcher
data-positionleft or right (default right)

You can also set the same keys on a window.crusoeChatSettings object before the script loads. For programmatic control, the widget exposes a tiny API:

window.CrusoeChat.open() window.CrusoeChat.close()
window.CrusoeChat.toggle() window.CrusoeChat.reset()

reset() forgets the visitor's identity and transcript — useful behind a "start over" button.

Manage the key

Two verbs are worth separating before you use either. Rotating mints a new key and kills the old one, keeping everything else. Removing deletes the key, the appearance settings, and the domain list together — and enabling the widget again after that mints a different key.

platformctl agents embed get my-agent # read the config, key included
platformctl agents embed rotate-key my-agent # new key, everything else kept
platformctl agents embed disable my-agent # remove the widget entirely

rotate-key prints the new key on a line of its own, because it is what has to go into your snippet right now:

new embed key: emb_yyyyyyyyyyyyyyyyyyyyyyyy
update the widget snippet on every page that embeds my-agent - the previous key no longer works
embed disable is a removal, not a toggle

Despite the name, platformctl agents embed disable deletes the whole configuration, key included — it is the CLI equivalent of the console's Remove widget, not its Disable. To turn the widget off and keep the key, use the console, or PUT the configuration back with "enabled": false.

Rotating breaks every pasted snippet immediately

The old key stops working the moment you rotate. Every site still carrying the old snippet goes dark until someone pastes the new one. Rotate when a key is abused or leaked into the wrong hands — and update your sites right after.

Rotations, removals, and configuration changes are all recorded in the project's audit log (agent.embed.update, agent.embed.rotate-key, agent.embed.delete).

The public paths the widget itself uses are not on this management surface at all. The console serves GET /embed/{key}/config and POST /embed/{key}/invoke on console.codyhill.dev, anonymously and cross-origin, and forwards them to the platform API. Those two are the only calls a visitor's browser ever makes.

Limits

LimitValue
Embed key shapeemb_ + exactly 24 lowercase letters/digits (anything else: invalid embed key)
Message body1 MiB
Reply waitWidget waits 70 s; the server allows 90 s
Config fetch15 s
Stored transcriptLast 50 messages, in the visitor's browser

Troubleshooting

  • The widget doesn't appear at all. A key that is wrong, disabled, or rotated away renders nothing, by design. It logs a single warning in the browser's developer console. Check the key in your snippet against the Embed tab.
  • "invalid embed key". The key in the snippet doesn't match the required shape — usually a truncated copy-paste.
  • Works on one site but not another. The failing site's origin is missing from the allowlist, or was entered with a path. Enter https://host only.

Next steps