Guides

Embed a support widget as a launcher, page, or pane

Heliune widgets are workspace objects. Try chat persists the object and runs the attached flow before Install; requireEmail can ask for name and email before the first line; the public snippet still opens an inbox thread and can land on a named queue.

A Heliune widget is a workspace object, not a second inbox. You draft it in the widget builder or from the builder agent in the rail. Try chat persists that object and runs the attached flow before any snippet leaves the workspace. Install copies a snippet. Visitor messages open a widget-channel thread in the same inbox the team already works.

The live frame at /widget/{id} is noindexed on purpose. Search should find this note, not the chat iframe. The public boot script is /widget/{id}.js. The mount id is heliune-chat. Appearance uses a token theme plus optional CSS on stable heliune-widget-* hooks — not the workspace chrome.

Three embed placements

Placement is a field on the same object. You do not create three widgets to get three shapes. You pick floating, page, or pane, then copy the matching snippet.

  • Floating: a script before </body> mounts a launcher. The open window is a corner panel, a centered popup, or an edge sidebar.
  • Page: an iframe for a dedicated support or help URL. Give the host a height; the chat fills it. The snippet uses a 640px minimum.
  • Pane: the same iframe pattern for a product sidebar, help drawer, or in-app inbox. The snippet uses a 480px minimum.

What the visitor sees before they type

Pre-chat can be empty, the greeting, greeting plus starter chips, or the first message step of the attached chat flow. An optional bubble sits outside the launcher with a hint and FAQ chips. Empty pre-chat means the thread stays blank until the visitor types. Flow-start means the live graph has already spoken as the widget name before anyone in the inbox is assigned.

Starter chips are suggested questions you edit on the widget. They are not a second knowledge base. Clicking one sends that text as the first visitor message and opens the inbox conversation like any other first line.

How a message reaches the inbox

Each visitor keeps a thread keyed by widget id and visitor id. The first message opens an inbox conversation on the widget channel. Later messages append to that conversation. If a live flow is attached, the graph can reply as the widget and record a flow run on that conversation.

The same visitor id is how the workspace recognizes a return. A second visit is not a new stranger if that id already opened a thread on this widget — or if the same email already exists on another conversation. The public frame does not ask them to sign in. The id in the snippet is the key.

A handoff node stops the graph, sets the conversation to pending, and writes a real queue — General by default, or the named queue on that node. A person in the inbox then owns the thread. Once a teammate has replied, or the flow has handed off, later visitor lines do not restart the bot. Draft and paused flows do not run on the public frame. Toggle Widget active on Install before you expect the public frame to answer — that switch writes enabled and publishes the workspace immediately. Try chat is the workspace test. It does not turn the public frame on. Copying the Install snippet is the workspace onboarding signal that the embed left the builder.

Try chat before Install

Widgets and the widget builder both have a Try chat button. The card on /widgets is still a silent thumbnail — interactive is off, so that picture does not send. Try chat opens a dialog named Try chat · the widget name. The line under it is the rule: send a message to run the attached flow. You do not need to install this on your site.

The dialog mounts the same chat runtime the public frame uses, with live on. You switch floating, page, or pane. New test increments a preview visitor scope (preview:0, then preview:1). That mints a new visitor id in local storage, keyed apart from the id a host snippet would store. The next line is a new test thread, not a resume of the last one.

A signed-in owner can Try chat while Enabled is off. The public resolver still refuses a disabled object: /widget/{id} stays a dead frame, and the public widget lookup will not serve it. Inbox resolve has a second door. If you are signed into the workspace that owns the widget, the message still posts. That is why a draft widget can answer you in the workspace and stay dark on the site.

Both Try chat buttons persist first. The builder upserts the form, writes the workspace, and publishes the widget list so the live inbox is not talking to an object that only exists in the tab. If the attached flow is still draft, the builder also sets that flow live — same as picking the flow in the editor. The list-page button publishes the workspace that is already saved. It does not flip a draft flow. Hit Try chat from the builder when the graph is the thing you just attached.

The first Try chat line still opens a widget-channel conversation. A live flow still replies as the widget name. Handoff still sets pending and writes General or the named queue. Draft and paused graphs still do not run. The rail does not grow a try_chat tool. You open Widgets or the builder and press the button.

Email before the first message

requireEmail is a field on the same object. Default is off. When it is on, the composer shows Name and Email before Send will go. The client will not post until the email parses. The inbox route still checks: if the widget asks for email and this visitor id has no customerEmail yet, the reply is 400 — Email is required to start a conversation. A returning thread that already stored an email skips the form.

The name and email sit in the widget-seen store, scoped the same way as the preview visitor id. Try chat with requireEmail on still asks. A public visitor who already confirmed on this widget id does not fill the fields again. The list-card thumbnail will not show the proactive bubble when requireEmail is on — those FAQ chips would skip the gate — so the card opens the panel instead.

The rail writes the same config

The rail’s create_widget and update_widget tools write the same config the manual builder edits: placement, display mode, greeting, starters, attached flow, theme, custom CSS, enabled, requireEmail. list_workspace shows the widget next to flows and automations so the agent does not invent a twin.

If the draft is wrong, open the widget builder. The object in the workspace is the source of truth. The public snippet reads that object. There is no hidden “AI widget” that bypasses install.

Plans and the mark

Free workspaces get one widget and show the Heliune mark. Starter gets three. Pro and Business are unlimited. Paid plans drop the mark. Voice minutes are unrelated to the embed: the widget can still open a thread on Free; listen / whisper / barge sit on Pro when the conversation becomes a voice room.

What to install, and what not to index

Install is a copy button in the workspace. Floating injects the boot script. Page and pane inject an iframe pointed at the public frame. Do not paste the workspace URL. Do not ask search engines to index /widget/{id} — robots and metadata already noindex that route, the inbox, and /api.

If the snippet is on the site and Enabled is off, the visitor sees a dead frame. Try chat still works for a signed-in owner of that object. If Enabled is on and no live flow is attached, the first message still opens the inbox and waits for a person. That is a working install, not a failed bot.

A flow run is optional, not the product

Teams treat the embed as a bot and then wonder why the inbox is empty. The inbox is the product. The flow is an optional graph on that conversation. Message nodes speak as the widget. A condition branches. An action can call something you wired, including a route.queue action that moves the thread without handing off. Handoff is how a person takes the thread. Delay waits. Lookup and AI steps are also first-class nodes on that same graph. None of that replaces resolve, snooze, or a supervisor on a later call.

If you want the first screen to be the flow, set pre-chat to the first live step. If you want a human to speak first, leave pre-chat empty and keep the flow off or paused. Both are valid. The snippet does not change. The workspace object does.

Queues are workspace objects, not a hidden bot lane

Every workspace already has General. It is the catch-all queue. An empty roster means everybody who can take chats works it. A named queue is a roster you edit on Team. The widget thread sits on General unless a handoff or a route.queue action wrote another queue id onto that conversation.

The inbox context panel on the open thread can change queue and assignee after the fact. Save applies both. That is the same conversation the widget opened — not a cloned ticket in another product. If the frame talks but no person can see it, you are looking at a different workspace than the one that owns the object, or at a named queue your account is not on.

Returning visitors and open work

When a live flow runs on a widget message, the workspace loads visitor facts before the graph walks. Returning is true when another conversation already matches this visitor id or email. The facts also carry prior conversation count, prior subjects, and open tasks on those threads (to do or in progress).

A lookup node writes those facts into the run so a later condition can branch on visitor.returning, visitor.hasOpenTasks, visitor.priorConversationCount, and the rest of the visitor.* fields. You do not scrape the host page for this. You do not ask the visitor to log in. The widget id plus visitor id is enough.

An AI decide step picks a path from a schema you edit. The default outcomes are returning visitor, has open tasks, and new visitor. An AI generate step writes a reply from the same facts and can send it as the widget. If that step cannot run, the visitor message is still in the inbox. The bot failing is not a lost thread.

Waiting is not paused

A message node that ends in a question — or sets awaitReply — stops the graph and waits for the next visitor line. The conversation stores waiting plus the node id. The next message on that widget id and visitor id resumes from there. A completed flow does not loop the visitor through the graph again. A paused or draft flow never starts. Enabled off keeps the public frame itself from looking live. Try chat is a fifth switch, and it only exists in the workspace. Do not treat them as one “bot off” toggle.

Theme and CSS stay on the widget

The visitor should not see workspace chrome. Theme tokens and optional CSS on heliune-widget-* hooks style the frame they see. Do not paste your app’s global stylesheet into the snippet and hope. The host page keeps its own layout. The widget keeps its own tokens.

If you change greeting copy in the builder, the next visitor sees it. You do not redeploy your marketing site. That is the point of a workspace object: install once, edit the object, the public frame follows.

When a call starts from the same thread

The embed is chat. The workspace can still open a voice room later. The supervisor who listens is looking at the same inbox conversation the widget opened. Do not install a second “voice widget.” Voice minutes are a plan entitlement, not a second snippet.

Defaults you get before you edit

A new object greets with “Hi — how can we help?”, launches as “Chat with us”, sits bottom-right, opens as a panel, uses pre-chat greeting, and turns the outside bubble on. The bubble message copies the greeting unless you write a shorter hint. FAQ chips on a new object start empty unless you already set starter prompts.

Position can move to another corner. Display mode can become popup or sidebar. Pre-chat can become none, starters, or the first live flow step. None of those changes create a second object. They patch the same config the public frame reads.

Enabled defaults on. That surprises people who copied a snippet from a draft they were still designing. If you do not want the public frame to answer yet, turn Enabled off before Install. Draft flows already stay quiet; Enabled off keeps the public frame itself from looking live. Try chat still runs for you. requireEmail defaults off — turn it on in Inside the chat when the inbox should not start as Visitor.

A worked install

Create the object. Set placement to pane if the chat belongs inside billing. Attach a live flow only if the first screen should speak as the widget name. Press Try chat. Send one line. The inbox should show a widget-channel conversation before any snippet leaves the workspace. If requireEmail is on, fill Name and Email first — Send stays disabled until the email parses. Then copy the pane iframe. Give the host at least 480px. Confirm the widget id in the src matches the object you just saved.

Open the public frame yourself only after Enabled is on. Send one line. The inbox should show a widget-channel conversation keyed by that widget id and your visitor id — a different visitor than the preview: scope Try chat used. If a live graph is attached, the flow run appears on the flow and as an inbox event. If a lookup or AI step ran, the run steps name those nodes. If handoff ran, status is pending and the queue is General or the queue on that node. Reply from that row. Resolve when you are done.

If the inbox is empty, the id is wrong, Enabled is off, or you pasted the workspace URL. If the frame talks and the thread sits on a named queue you are not in, open Team and check the roster — do not assume the bot hid the conversation.

What Free versus paid changes on the frame

Builder-message budgets and voice minutes do not change the snippet. They change what the rail can draft and whether a later room can be supervised. Custom CSS is still sanitized. Theme tokens merge; they do not import your app’s global sheet. When you are choosing a vendor, ask one question: does the first visitor message open a conversation the same team can resolve, snooze, route to a queue, and later take as a voice room?

What the snippet actually points at

Floating injects /widget/{id}.js with a data-heliune-widget attribute. Page and pane iframes hit /widget/{id}?embed=page or embed=pane. Floating can also load the frame with embed=floating. Do not rewrite those query values by hand and expect a fourth placement. The parser only accepts floating, page, and pane.

Install copy in the workspace already says the host steps: paste before </body> for floating; give the page iframe a height (the snippet’s min-height is 640px); size the pane host (480px). Widget active on Install upserts the widget and publishes the workspace as soon as you flip it — there is no second Save on that toggle. Copying the snippet is not a deploy. Enabled on is.

The public origin comes from the app URL. If that origin is wrong, the visitor loads a frame that is not your workspace. Check the src before you ship the marketing site. The inbox will not show a thread for a snippet pointed at the wrong host.

More guides