Skip to main content

Chatbot

How to embed and configure the Flourish Agent Platform Chatbot component.

Overview

The Chatbot is a conversational chat interface connected to the Flourish Agent Platform. Add it to any Salesforce page (a record page, an app page, a community) and it connects automatically to the agent you specify.

The Chatbot can also be embedded on external websites outside of Salesforce using an HTML snippet generated from the Agent Builder. See Embedding on External Websites for details.

Out of the box, the Chatbot handles streaming responses, markdown rendering, file attachments, file downloads, dark mode, and automatic reconnection if the connection drops.


Adding the Chatbot to a Salesforce Page

In App Builder, drag the Chatbot component onto your page layout and set the Agent ID property to either a Salesforce Agent record ID or a Flourish Agent ID. The component resolves both automatically.

On record pages, the component automatically picks up the page's record ID and passes it through to the agent as context.


Properties

PropertyDefaultDescription
Agent ID-Required. A Salesforce Agent record ID or a Flourish Agent ID.
Record ID-Populated automatically on record pages. Used as a fallback if Agent ID is not set.
Height ModeMediumControls the component height: Small (400px), Medium (600px), Large (800px), or Custom.
Custom Height600pxUsed when Height Mode is set to Custom. Accepts any CSS height value (e.g. 600px, 80vh).
Hide FooterOffHides the bottom utility bar (Restart, Contact, Report) and the "Powered by Flourish" footer.
Custom Style-A JSON string of styling overrides. See Custom Styling.
Context-Structured data passed to the agent at runtime. See Passing Context.

Note: Height Mode and Custom Height are available when the component is added programmatically in code. They are not exposed as design properties in App Builder.


Passing Context

The Context property lets the host page pass structured data to the agent at runtime. The agent receives this data as part of every conversation turn, which is useful for providing record data, user details, or any other information the agent should be aware of.

Within the agent's Profile or Startup Instructions, reference context values using {{{key}}} template syntax.

See the For Developers section for how to set context programmatically.


Custom Styling

You can brand, resize, and slim down the Chatbot with a single JSON value (the custom-style property). It has three sections:

  • colors: palette overrides
  • show: hide or show individual components
  • layout: width, height, and shape

Colors

KeyWhat it colors
accentThe brand color: send button, links, activity spark, focus ring
accentPressSend button hover
onAccentThe icon on the send button
groundBackground behind the conversation
surfaceHeader bar, composer surface, menus
surface2Table headers, disabled send, subtle fills
border / borderStrongHairlines / the composer's outer border
textPrimary / textMuted / textFaintText hierarchy
userBubble / userTextThe user's message bubble
focusRingGlow around the composer when focused

Any color is either a plain string (applies in both themes) or a { "light": …, "dark": … } pair. Dark values switch in automatically with the dark-mode toggle. There is no separate dark section.

Show / Hide Components

Set any of these to false under "show" to hide it: header, avatar, utilityBar, disclaimer, poweredBy.

Layout

KeyPurpose
width, height, minHeight, maxHeightSize of the whole chatbot frame. "fill" is shorthand for 100%.
bodyHeight, bodyMinHeight, bodyMaxHeightSize of the scrolling message area specifically. bodyMinHeight prevents the chat from collapsing when there are few messages.
radius, headerRadiusOuter container / header corner radius (set to 0 for flush embeds)

"fill" makes the chatbot match its parent, which only works if the parent element actually has a size. Width almost always does; height often doesn't in content-height page regions, where bodyMinHeight is the better tool.

Example:

{
"colors": {
"accent": { "light": "#7C3AED", "dark": "#8B5CF6" },
"userBubble": "#EDE9FE"
},
"show": { "poweredBy": false },
"layout": { "height": "fill", "bodyMinHeight": "50vh" }
}

Dark Mode

The chatbot has a built-in dark mode toggle in the header (a sun/moon icon). The user's preference is remembered in their browser and restored on the next visit.

If your Salesforce environment restricts browser storage, dark mode still works for the session. The preference just won't persist across page loads.


Streaming Responses

The chatbot streams responses in real time. As the agent generates its reply, words appear progressively, with no waiting for a full response before anything shows.

While the agent works, an animated activity spark with a "Thinking…" label appears in the conversation. Internal details such as reasoning steps and tool names are deliberately not shown to end users.

A Stop button replaces the Send button while a response is being generated. Clicking it immediately cancels the response.

Progress Notifications

While the agent is working (between tool calls or reasoning steps), it may send brief progress notifications: short status messages that appear inline in the chat. These are styled as lightweight italic text, distinct from the agent's final response, so you can see what the agent is doing without waiting for the full answer.

Progress notifications appear automatically. No configuration needed.


File Uploads

When File Upload is enabled in the Files group on the agent's Tools tab, users see a file attachment button (paperclip icon) next to the text input. Files are uploaded to the conversation and become available to the agent for processing via run_code scripts.

How It Works

  1. Click the attachment button and select one or more files (up to 10 per message, 30 MB each).
  2. Selected files appear in the attachment strip above the input: images as thumbnails, other files as compact cards.
  3. Type a message and click Send. The files upload to the server first, then the text message is sent.
  4. While uploading, each file card shows an upload indicator (spinner for files, overlay for images). The indicator clears when the upload completes.
  5. The agent can access uploaded files in its run_code scripts.

If an upload fails (e.g. file too large, network error), the file card shows an error indicator and an error message appears briefly. The text message is still sent even if some uploads fail.


File Downloads

When the agent generates a file (via the File Generation tool and outputs.save() in a run_code script), a download card appears inline in the chat. The card shows the filename, file size, and a Download button.

Clicking Download fetches a fresh download link and opens the file in a new tab. Generated files are retained for 180 days.


Markdown Rendering

Assistant responses are rendered as formatted Markdown, including headings, bold and italic, code blocks, inline code, lists, tables, blockquotes, and links. Links always open in a new tab.

A copy button appears on hover over any assistant message, letting users copy the raw markdown to their clipboard.

If the markdown renderer fails to load, the component falls back to basic inline formatting so the chat remains functional.


Conversation History

When a user returns to the same page (or the same agent in the same session), the chatbot can resume a previous conversation by replaying the message history. This happens transparently. No user action required.

To force a brand-new conversation, click Restart in the footer utility bar.


Utility Bar

The footer utility bar (visible when Hide Footer is off) provides three actions:

ButtonAction
RestartClears the chat and starts a fresh conversation
ContactOpens a contact form for the user to submit their name, email, and a message
ReportOpens an issue report form with a description field and optional email

The Contact and Report forms are starter templates. A developer can connect them to Salesforce logic such as Case creation.


Connection & Reconnection

The chatbot establishes a live connection to the Agent Platform when it loads. If the connection drops unexpectedly, it automatically retries up to 3 times (waiting 2, 4, and 6 seconds between attempts).

Some errors (authentication failures, credential issues, or an agent that can't be loaded) can't be resolved by reconnecting. In those cases, the chatbot shows an inline error message instead of retrying.


Embedding on External Websites

You can embed the chatbot on any website outside of Salesforce: a marketing site, a help center, a client portal, or any page where you can add HTML.

Prerequisites

Before you can generate an embed snippet, two things must be in place:

  1. A Salesforce Site page that hosts the Chatbot component. This is a bare-bones page with nothing but the chatbot, with no Salesforce navigation, headers, or chrome. Your Salesforce admin sets this up once, and the URL is stored in the Chatbot_Embed_Site__c field on FL_Intelligence__mdt.

  2. A registered, deployed agent. The embed snippet references the agent's Agent ID, so the agent must be registered on the Agent Platform.

Once both are configured, the Embed button appears in the Agent Builder action bar.

Generating the Snippet

  1. Open the agent in the Agent Builder.
  2. Click Embed in the action bar.
  3. Choose an embed mode:
    • Bubble: a floating chat button in the bottom-right corner of the page. Clicking it opens a chat panel. You can customize the button color.
    • Inline: an iframe embedded directly in the page flow. It auto-resizes to fit the chatbot's content height.
  4. If using Bubble mode, pick a Button Color using the color picker.
  5. Optionally enable Test mode (see below) to validate the embed on a live site before launching.
  6. Click Copy to Clipboard and paste the snippet into your website's HTML.

The snippet is a single script tag. All of the widget's logic lives in a centrally hosted script, so fixes and improvements reach your site automatically and you never need to re-paste.

Where to Paste the Snippet

Bubble mode: Paste the tag just before the closing </body> tag. It creates the floating button and chat panel automatically, with no container element needed.

<!-- Flourish Chatbot -->
<script src="https://cdn.toflourish.org/agent-platform/v1/chatbot-embed.js" defer
data-agent-id="YOUR_AGENT_ID"
data-embed-url="https://yourorg.my.site.com/chatbot"
data-mode="bubble"
data-accent="#2A809C"></script>

Inline mode: Paste the snippet wherever you want the chatbot to appear. It builds its own container in place, sized by data-height and data-max-width.

<!-- Flourish Chatbot (inline) -->
<script src="https://cdn.toflourish.org/agent-platform/v1/chatbot-embed.js" defer
data-agent-id="YOUR_AGENT_ID"
data-embed-url="https://yourorg.my.site.com/chatbot"
data-mode="inline"
data-accent="#2A809C"
data-height="600"
data-max-width="480"></script>

Snippet attributes

The Embed modal writes these for you. They are the loader's contract, so the spellings matter.

AttributeDefaultWhat it does
data-embed-url-Your Site URL. Required: with nothing to point at, the loader renders nothing at all rather than a broken shell.
data-agent-id-The agent to open. The loader appends it to your Site URL as c__flagentid.
data-modebubblebubble for the floating launcher, inline to sit in the page.
data-accent#2A809CAccent color, used for the launcher button.
data-positionrightleft or right. Bubble mode only.
data-labelLet's chat!Text on the launcher button.
data-titleChatTitle shown on the panel.
data-height640Height in pixels.
data-max-widthnoneWidth cap in pixels. Leave it off to fill the space.
data-test-Hides the widget from everyone but you. See Test mode.
data-flstyle-Custom styling JSON, or off for the loader's plain defaults.

Passing the Agent ID

The snippet carries the agent ID in data-agent-id, and the loader appends it to your Site URL as the c__flagentid parameter. You only need to handle this yourself if you're building the iframe by hand or choosing the agent at runtime.

Your own iframe: put the parameter on the Site URL directly.

<iframe src="https://yourorg.my.site.com/chatbot?c__flagentid=YOUR_AGENT_ID"
allow="clipboard-write"></iframe>

To choose the agent at runtime instead of hardcoding it, see Selecting the Agent at Runtime.

Four things to know:

  • URL parameters are read only in an iframe. The chatbot checks whether it's embedded before reading them, so a component placed directly on a Lightning page ignores them and uses its design properties instead.
  • A set property wins over the parameter. The URL value is applied only when the Agent ID property is empty, so leave that property blank on the Site page you use for embedding. Otherwise every embed gets the hardcoded agent no matter what the URL says.
  • Use the platform Agent ID, the value behind Copy Agent ID in the Agent Builder action bar. A Salesforce record ID also resolves, but it means nothing to an external site.
  • Treat the ID as public. It's visible in your page source and anyone can change it in the URL, so the agent needs to be shared with whoever will reach it (public access for anonymous website visitors). Don't rely on the ID being unguessable.

The Site URL accepts three parameters in total. Each also works without the c__ prefix (flagentid, flstyle, flparentorigin) if that's easier for your host.

ParameterPurpose
c__flagentidWhich agent to load. Set automatically from data-agent-id when you use the loader.
c__flstyleURL-encoded JSON using the Custom Styling contract.
c__flparentoriginYour page's origin, needed for the Host API when your host strips the referrer.

Test Mode

Enable the Test mode toggle in the Embed modal to add data-test="1" to the snippet. With it in place, the chatbot stays completely hidden for normal visitors. It only activates for someone who opens a page with ?testflchatbot=1 in the URL, and it then stays active as they navigate around the site for the rest of their tab session.

This lets you put the snippet on your production website and test it end-to-end before anyone else can see it. To launch for everyone, remove the data-test attribute (or re-copy the snippet with Test mode off).

Bubble Mode Behavior

The bubble embed creates two elements on your page:

  • A floating button (56px circle) fixed to the bottom-right corner. Clicking it toggles the chat panel open and closed. The button icon switches between a chat icon (closed) and an X (open).
  • A chat panel (400×600px) that slides in above the button with a subtle animation.

On mobile screens (under 480px wide), the chat panel expands to fill the entire screen for a better experience.

When the agent sends a new message while the panel is closed, the button shows a small notification dot (driven by fl__notify events from the chatbot).

Inline Mode Behavior

The inline embed places the chatbot directly in the page flow as an iframe inside your container. The script listens for height-change messages from the chatbot and resizes the container automatically, so the iframe never shows scrollbars or wastes space.

Customizing the Embed

Changing the bubble color: Use the color picker in the Embed modal before copying, or edit the data-accent attribute on the tag.

Changing the inline size: Adjust data-height and data-max-width on the tag. Both are plain pixel numbers.

Passing custom styles to the chatbot: Put a URL-encoded JSON string on data-flstyle, using the Custom Styling contract, or add a c__flstyle parameter to the data-embed-url value. For example, to keep the message area from collapsing: c__flstyle=%7B%22layout%22%3A%7B%22bodyMinHeight%22%3A%2250vh%22%7D%7D.

Older embeds: Sites embedded with the previous self-contained snippet (a full inline script rather than a one-tag loader) keep working. They just don't receive central updates. Re-copy the new snippet whenever convenient.

Writing code against an embed? The events the iframe exchanges with your page, the methods for driving the conversation from the host, and receiving agent actions are all covered under Iframe Integration.


For Developers

This section covers programmatic usage of the Chatbot: embedding it as a child Lightning Web Component (LWC) inside a custom page or another component, and integrating with it from a page that hosts it in an iframe. Everything involving code, events, or the Host API lives here. See Iframe Integration for the cross-frame equivalents.

Basic Markup

<c-intel_chatbot
agent-id="a01xx0000001234AAA"
record-id={recordId}
height-mode="medium"
hide-footer={false}>
</c-intel_chatbot>

You can pass either a Salesforce record ID (pointing to an Agent__c record) or a Flourish Agent ID directly to agent-id. The component resolves both automatically.

Property Reference

PropertyTypeDefaultDescription
agent-idString-Required. Salesforce Agent record ID (15 or 18 chars) or Flourish Agent ID.
record-idString-Passed automatically on record pages. Fallback if agent-id is not set.
height-modeStringmediumComponent height: small, medium, large, or custom.
custom-heightString600pxUsed when height-mode is custom. Any CSS height value.
hide-footerBooleanfalseLegacy: hides the utility bar. Prefer show.utilityBar / show.poweredBy in custom-style.
custom-styleString-JSON styling contract (colors / show / layout). See Custom Styling.
object-api-nameString-Auto-populated on record pages. Identifies the host record's object type (e.g. Account).
contextObject-Opaque data object passed to the agent at runtime.

Setting Context Programmatically

Pass structured data to the agent from the parent component. When the value changes, the chatbot automatically sends the updated context.

// In the parent LWC's JS
get chatbotContext() {
return {
recordId: this.recordId,
accountName: this.account.Name,
userRole: this.currentUserRole,
};
}
<c-intel_chatbot
agent-id="your-agent-uuid"
context={chatbotContext}>
</c-intel_chatbot>

context lands in the agent's host context, which the platform treats as read-only ambient background (used to interpret references like "this record"), not as a data source the agent answers from. To hand the agent data it should actively use, prefer attachData plus sendMessage (below).

Host API Methods

A same-document parent LWC can grab the component via this.template.querySelector (or refs) and call these imperative methods directly. Across an iframe embed, the same three are driven over postMessage. See Driving the Chatbot from the Parent Page.

MethodSignaturePurpose
sendMessagesendMessage(text, { origin })Sends a message. origin: 'user' (default) renders a user bubble; origin: 'host' delivers a state-change notification (no bubble, and the think loop decides whether to respond). Returns true if sent.
attachDataattachData(name, data, contentType?)Stages in-memory data (string, JSON-serializable object, or Blob) as a conversation upload the agent reads via uploads["<name>"] in run_code. Keeps bulk data out of the LLM context window. Returns a Promise<boolean>. Requires a live session.
setHostBusysetHostBusy(isBusy, label?)Raises or clears the host's own busy state (locks the composer, shows the indicator with label). Independent of the agent's own busy state: it can raise but never clear the agent's.
// In the parent LWC's JS: hand the agent local data, then ask it to use it.
async askOverResults(question, results) {
const chatbot = this.template.querySelector('c-intel_chatbot');
chatbot.setHostBusy(true, 'Searching…');
await chatbot.attachData('results.json', results, 'application/json');
chatbot.sendMessage(
`Attached results.json (${results.length} hits). Answer "${question}" from it, citing sources.`,
{ origin: 'host' },
);
chatbot.setHostBusy(false);
}

Handling Agent Actions

The chatbot dispatches an agentaction custom event when the agent sends a structured action during its response. Actions are delivered via two mechanisms; both produce the same agentaction event on the host side:

  1. Structured delivery (preferred): agents with Custom Actions configured send actions as typed events. The chatbot receives them and dispatches each as an agentaction event.
  2. Text-based delivery (legacy): agents can embed :::action ... ::: blocks in their response text. The chatbot parses these, strips them from the displayed message, and dispatches them as events.

The agentaction DOM event reaches a same-document LWC parent. When the chatbot runs in an external iframe, the same action is also posted to the parent window as an fl__action message. Both surfaces fire, from both delivery paths.

handleChatEvents(event) {
const action = event.detail;
console.log(action.type, action);

if (action.type === 'navigate_record') {
this[NavigationMixin.Navigate]({
type: 'standard__recordPage',
attributes: { recordId: action.record_id, actionName: 'view' },
});
}
}
<c-intel_chatbot
agent-id="your-agent-uuid"
onagentaction={handleChatEvents}>
</c-intel_chatbot>

Action blocks are parsed and stripped before the response text is displayed. Users never see the raw syntax.

Custom Style Markup

The custom-style value uses the namespaced contract described in Custom Styling: colors / show / layout, with any value accepting a { "light", "dark" } pair.

<c-intel_chatbot
agent-id="your-agent-uuid"
custom-style='{"colors": {"accent": "#7C3AED"}, "layout": {"radius": "0"}}'>
</c-intel_chatbot>

Embedding Examples

Flush full-height embed (fill a sidebar panel):

<c-intel_chatbot
agent-id="your-agent-uuid"
custom-style='{
"show": { "utilityBar": false, "poweredBy": false },
"layout": { "height": "fill", "radius": "0", "headerRadius": "0" }
}'>
</c-intel_chatbot>

Branded embed (match your org's color scheme):

<c-intel_chatbot
agent-id="your-agent-uuid"
custom-style='{
"colors": {
"accent": { "light": "#0F766E", "dark": "#0D9488" },
"userBubble": { "light": "#CCFBF1", "dark": "#134E4A" }
}
}'>
</c-intel_chatbot>

Iframe Integration

When the chatbot runs in an iframe on an external site, your page talks to it over postMessage rather than through component properties and DOM events. The exchange is symmetric with the LWC surface above: the same Host API methods drive the conversation, and the same agent actions come back.

postMessage Events

The hosted script (chatbot-embed.js) reads its configuration from the data-* attributes on its own script tag, then loads the chatbot in an iframe pointing to your Salesforce Site page, passing the agent ID as a URL parameter (c__flagentid). The chatbot detects that it's running inside an iframe and reads its configuration from the URL instead of from Salesforce component properties.

The iframe communicates with the parent page using postMessage:

EventDirectionPurpose
fl__readyChatbot → ParentThe chatbot's DOM has loaded (fires early, before the chat session exists)
fl__sessionreadyChatbot → ParentThe chat session is live, so the Host API (send message / attach data) is now safe to call. Re-fires on reconnect.
fl__resizeChatbot → ParentThe chatbot's content height changed (inline mode uses this to resize the wrapper)
fl__notifyChatbot → ParentThe agent finished a response (bubble mode can use this for a notification indicator)
fl__actionChatbot → ParentThe agent invoked a Custom Action. Payload { action: 'fl__action', payload: { type, ...params } }, the cross-frame equivalent of the agentaction DOM event.

All messages are JSON-encoded and origin-checked for security. fl__action is posted only to the validated parent origin (never *), since action payloads carry data. If the parent origin can't be determined, the action is not forwarded.

Driving the Chatbot from the Parent Page (Host API)

Beyond receiving events, the parent page can drive an embedded chatbot: send messages, hand the agent data, and show a busy state. This lets the host page act as an orchestrator. For example, run a local search, hand the results to the agent, and have it answer from them.

Because an iframe boundary separates the parent from the chatbot, the parent cannot call the component's methods directly. Instead it posts JSON messages to the iframe; the chatbot validates the sender origin, then invokes the matching method. If you use the hosted chatbot-embed.js loader, a convenience wrapper is exposed as window.FlourishChatbot. If you host your own iframe, post the raw messages yourself.

Parent to chatbot messages:

actionPayloadEffect
fl__sendMessage{ text, origin? }Sends a message. origin: 'host' (default from the bridge) delivers it as a state-change notification: no user bubble, and the agent's think loop decides whether to respond. origin: 'user' injects it as if the user typed it (renders a bubble).
fl__attachData{ name, data, contentType? }Stages in-memory data (string, JSON-serializable object, or Blob) as a conversation upload. The agent reads it via uploads["<name>"] in run_code, so data never enters the LLM context window until the agent computes over it. Up to 30 MB.
fl__setHostBusy{ busy, label? }Raises or clears the host's busy state (locks the composer, shows the activity indicator with label). The host controls only its own flag. It can never clear the agent's own "thinking" state, so it cannot falsely signal "ready for input" mid-response.

Messages posted before fl__sessionready are ignored, so wait for that event. fl__attachData followed by fl__sendMessage is safe to fire back-to-back: the chatbot serializes them so the upload completes before the message is sent.

Origin security. The chatbot only accepts messages from the parent origin it derives from document.referrer. If your host strips the referrer (Referrer-Policy), pass the parent origin explicitly by adding a c__flparentorigin parameter to the Site URL (e.g. c__flparentorigin=https://your-app.example.com). If no origin can be determined, all inbound messages are rejected.

Example: using the hosted loader's wrapper

<script src="https://cdn.toflourish.org/agent-platform/v1/chatbot-embed.js" defer
data-agent-id="49b3512c-…" data-embed-url="https://flourish.my.site.com/agent"
data-mode="inline"></script>
<script>
// FlourishChatbot is defined by the loader once the iframe mounts.
window.addEventListener('message', function (e) {
// (Optionally verify e.origin against your Site origin here.)
var msg; try { msg = JSON.parse(e.data); } catch (_) { return; }
if (!msg || msg.action !== 'fl__sessionready') return;

// Session is live, so it is now safe to drive the agent.
FlourishChatbot.setHostBusy(true, 'Searching…');
var results = /* run your local search */;
FlourishChatbot.attachData('results.json', results, 'application/json');
FlourishChatbot.sendMessage(
'Attached results.json (' + results.length + ' hits). Answer the pending question from it, citing sources.'
);
FlourishChatbot.setHostBusy(false);
});
</script>

Example: hand-rolled iframe (raw postMessage)

<iframe id="fl-chatbot"
src="https://flourish.my.site.com/agent?c__flagentid=49b3512c-…&c__flparentorigin=https://your-app.example.com"
allow="clipboard-write"></iframe>
<script>
var iframe = document.getElementById('fl-chatbot');
var CHATBOT_ORIGIN = 'https://flourish.my.site.com'; // the Site origin
function post(payload) {
iframe.contentWindow.postMessage(JSON.stringify(payload), CHATBOT_ORIGIN);
}
window.addEventListener('message', function (e) {
if (e.origin !== CHATBOT_ORIGIN) return;
var msg; try { msg = JSON.parse(e.data); } catch (_) { return; }
if (msg && msg.action === 'fl__sessionready') {
post({ action: 'fl__setHostBusy', busy: true, label: 'Searching…' });
post({ action: 'fl__attachData', name: 'results.json', data: myResults, contentType: 'application/json' });
post({ action: 'fl__sendMessage', text: 'Attached results.json. Answer from it.', origin: 'host' });
post({ action: 'fl__setHostBusy', busy: false });
}
});
</script>

Note on host-origin messages. origin: 'host' messages are delivered to the agent tagged as host notifications rather than user turns, and are hidden from the visible transcript (live and on history reload). Whether a host-origin message elicits a response is decided by the platform. If a message doesn't produce a reply, that is the expected behavior for a notification.

Receiving Agent Actions in the iframe

The reverse of fl__sendMessage: when the agent invokes a Custom Action, whether delivered as a structured action or a legacy :::action ... ::: text block, the chatbot posts it to the parent as an fl__action message. This is the cross-frame equivalent of the agentaction DOM event that a same-document LWC parent receives (see Handling Agent Actions). Both fire, so the action reaches whichever kind of host is embedding.

The message shape is { action: 'fl__action', payload: { type, ...params } }, where type is the action's action_type and the rest are its validated parameters, e.g. { type: 'query_corpus', query: 'venmo transfer', perPage: 15 }.

Receiving it (hosted loader):

// FlourishChatbot is defined by chatbot-embed.js.
const off = FlourishChatbot.onAction(function (action) {
// action = { type, ...params }
if (action.type === 'query_corpus') runSearch(action.query, action.perPage);
});
// off() to unsubscribe.

Receiving it (hand-rolled iframe):

window.addEventListener('message', function (e) {
if (e.origin !== CHATBOT_ORIGIN) return; // your Site origin
var msg; try { msg = JSON.parse(e.data); } catch (_) { return; }
if ((msg.type || msg.action) === 'fl__action') {
handleAction(msg.payload); // payload = { type, ...params }
}
});

fl__action is posted only to the validated parent origin (the one derived from document.referrer or the c__flparentorigin param), never *. Custom actions with no return value (navigate, filter, bookmark) are one-way UI drivers. To feed results back to the agent, run the work in the host and return via fl__attachData plus fl__sendMessage.

Selecting the Agent at Runtime

To serve a different agent per page, read the ID at runtime and inject the loader tag yourself instead of pasting a static snippet.

<script>
var agentId = new URL(location.href).searchParams.get('agent') || 'DEFAULT_AGENT_ID';
var s = document.createElement('script');
s.src = 'https://cdn.toflourish.org/agent-platform/v1/chatbot-embed.js';
s.defer = true;
s.dataset.agentId = agentId; // the loader turns this into c__flagentid
s.dataset.embedUrl = 'https://yourorg.my.site.com/chatbot';
document.body.appendChild(s);
</script>