Every setting the chat widget has, what it does, where you change it in the dashboard, and what your visitor actually sees. Written for support staff and customers, with screenshots and recordings from a real widget running against a real API.
An integration is an optional extra destination, never a requirement. Every submitted form is written to Assistable's own records, and whenever there is something a human would want to read, it also opens a conversation in your Inbox. That happens before anything is forwarded anywhere, whether or not a tool is chosen, whether or not Gleap, Zendesk, GHL or Stripe is connected, and even when the forward fails outright.
If you have not connected a helpdesk yet, set the form up anyway. It will work. Connect the helpdesk later and new submissions start flowing there as well, with no change to the form.
The snippet you paste onto your website carries only a widget id. On load
the widget calls
GET /api/v1/widget-config/<widget id> and takes almost
everything from the answer. That is why you never have to re-copy the
snippet after changing a setting.
It also means changes are cached: about 30 seconds in the visitor's browser and up to 5 minutes on the server. If a change does not appear immediately, wait a minute and hard-refresh before assuming the save failed.
allowed_domains, ghlSync and
ticketsEnabled are deliberately kept off the public
configuration so that one customer cannot read another's. They are enforced
server-side. A handful of other options can only come from the embed
snippet, and a handful of switches in the dashboard currently do nothing at
all. Both groups are flagged throughout with
and
no effect badges, and listed together in
section 23.
Open Portal › Widgets, pick your widget, and go to the
Install section (or press Get code in the
header, which produces exactly the same snippet). Paste it just before the
closing </body> tag of every page you want the widget on.
<script
src="https://app.assistable.ai/chat-widget-v2.js"
data-widget-id="cmqj7kubf0001rua7841r0345"
data-api-url="https://api.assistable.ai"></script>
The widget refuses to render at all when the configuration call fails or
when the returned widget id, assistant id or account id does not match the
id you embedded. That is a deliberate safety check. Three causes cover
almost every case: the widget is archived or deleted (the API answers 404
widget_not_found), the widget has no assistant assigned, or
data-api-url points somewhere unreachable. Open the browser
console; the widget logs a one-line reason such as
widget_config_http_error or widget_config_timeout.
Everything in this guide is set in one place: Portal › Widgets › (your widget). The editor has six sections down the left and a live preview on the right that updates as you type, before you save.
| Section | What lives there |
|---|---|
| Assistant | Which assistant answers, and the three messages the widget can override from it: popup message, intro message, fallback message. Suggested prompts are edited here too, but as a plain list with no Override switch. Also the door to the assistant's own knowledge bases and its shared messaging defaults. |
| Tabs & content | Which of the five tabs are shown, the Home header and action cards, the News / Roadmap / Help content, and the launcher teaser. |
| Appearance | Display name, company name, header subline, online dot, launcher icon, avatars, brand colour, teaser colour, light/dark, corner style, launcher size, position, auto-open. |
| Integrations & tools | Which tools the assistant may call. These toggles belong to the assistant, not the widget, so they apply on every channel that assistant runs on. |
| Behavior | Voice calling, GoHighLevel sync, human handoff and its mode, office hours, pre-chat form, notification sound, persist conversation, AI disclaimer, allowed domains. |
| Install | The embed snippet. |
Two conveniences worth knowing. The editor writes the open section into the
address bar as ?section=behavior (and on Tabs & content also
&card=help), so you can send a colleague straight to the
exact control. And Cmd/Ctrl+S saves.
Three of the widget's messages have two possible sources: the assistant (shared by every channel that assistant runs on) and the widget (this widget only). Each has an Override switch. Off, the field shows "Inherits from {assistant}: …" and follows the assistant. On, you type a value that applies to this widget alone. The teaser Greeting, over in Tabs & content, behaves the same way.
Suggested Prompts is not one of them. It has no Override switch: it is a plain list that simply replaces the assistant's prompts whenever you put anything in it, and falls back to the assistant's when you empty it.
Beside the Save button the header shows Ready to publish or N to set up. Hover it for the list. It flags: no assistant assigned, voice on without a voice agent, handoff on without Slack, no greeting, and no display or company name.
Save changes validates every home action before it saves
anything. A form card with no fields, or a link card with no URL, blocks the
whole save with a red toast such as
Invalid Home Actions: Action "Track my order" is a link but has no
URL. An unrelated broken card will therefore stop your new one from
saving too. On success you get Widget updated successfully.
Appearance › Icons & avatars › Launcher icon · five choices · button_icon
Five presets, shown here in the brand colour. The dashboard labels them
Chat, Message, Help, Support and AI; the stored values are
chat, message, question,
headset and sparkles. Anything else falls back to
Chat.
Appearance › Icons & avatars › Header & bot avatar · logo_url
Set an avatar and it covers the whole launcher circle while the widget is closed. The preset icon is then never seen. Two consequences that generate tickets: the launcher stops looking like your brand colour (the image covers it), and opening the widget swaps the logo for a chevron, so "my logo disappears when I open the chat" is expected behaviour, not a bug.
Appearance › Theme › Launcher size · Small / Medium / Large · launcher_size
48, 60 and 72 pixels across, with a 20, 24 or 28 pixel glyph inside. Medium is the default.
Appearance › Theme › Position · four corners · position
Bottom right is the default. The panel, the teaser and the launcher all move together.
bottom-lefttop-rightAppearance › Theme › Auto-open on page load · auto_open
The panel is open the moment the page finishes loading, on every page the snippet is on. Use sparingly; it is the single most intrusive setting in the widget.
auto_open on, the visitor never sees the launcher first.The teaser is the small card that floats above the launcher to invite people in. It is made of three separate settings and can be turned off entirely.
Tabs & content › Launcher teaser for the greeting and chips
· Assistant › Popup Message for the subtitle
· teaser_greeting, teaser_suggestions, idle_message
{display name} · Just now and cannot be changed.Clicking anywhere on the card body opens the chat. Only the small X dismisses it.
Appearance › Theme › Teaser bubble color · teaser_color
The teaser does not use your brand colour by default. Left
unset it is a near-black #1c1c1e. The dashboard has a
Match brand color link if you want them the same. Text
contrast flips automatically based on how dark the colour you pick is.
#1c1c1eteaser_color: #2563ebTabs & content › Launcher teaser › Show teaser bubble · show_prompt
Turn Show teaser bubble off and only the launcher renders. Clearing the greeting and the popup message and removing the chips has the same visible effect, but the switch is the direct control.
Dismissal only lasts for the browser tab. The X writes to
sessionStorage, so the teaser is back in a new tab or after the
browser is restarted. "It keeps coming back" is by design.
There is no delay setting. The card is held back by a fixed 350 millisecond delay, then fades up over 220 milliseconds, with the chips staggering in behind it from 400 milliseconds. Nothing in the dashboard changes any of that timing.
Home is the first thing a visitor sees. Top to bottom it is: the logo, the heading, the sub-heading, an ask box, then either your action cards or a pair of prompt chips, then the optional bug reporter.
Tabs & content › Home › Header / Sub-header · menu_header, menu_subheader
Leave the header blank and the widget falls back to "How can we help?". Leave the sub-header blank and it falls back to "We typically reply in a few minutes." There is no way to render Home with no sub-heading at all; you can only replace the wording.
Always present, always reading "Ask me anything...". There is no setting for this placeholder. Typing here and pressing Enter opens the chat with that question already sent.
Assistant › Suggested Prompts · prompts
Suggested prompts show on Home as chips, but only when there are no home action cards at all, and only the first two. Add a single action card and every chip vanishes from Home. They still show inside an empty chat, where all of them render.
The chip icons are chosen by position, not by wording: the first chip always gets a bug icon and the second always a lightbulb, whatever the text says.
Three settings people expect on Home are not there. The online dot, the header subline and the "Powered by" line all live in the chat panel, not the Home tab. So does the AI disclaimer. See section 17.
Tabs & content › Home › Home action cards · home_actions
Action cards are the buttons on Home. Each has a title, an optional subtitle, an icon, an optional accent colour, and a mode that decides what tapping it does.
| Mode | Dashboard label | What happens | When to use it |
|---|---|---|---|
chat | Open the chat | Opens the chat. If you set a Starter message it is sent to the AI immediately; otherwise the card's title is sent as the visitor's first message. | Anything conversational. |
form | Show a form to fill in | Opens a form built from the fields you define. On submit the answers are saved, and optionally forwarded to one destination. | Bug reports, feature requests, NPS surveys, lead capture. |
link | Send to another page | Opens a URL in a new tab. | Status pages, docs, booking pages, courier tracking. |
icon: "not-a-real-icon" and has silently fallen back to the
generic message bubble.
Add from template gives you five complete, working cards: Bug report, Feature request, NPS survey, Order status (commerce), and Ask a question (chat). Starting from a preset is easier than starting blank because the field keys are already correct.
form · create_support_ticket · 5 fields. Use the
pencil to expand and the bin to delete.
Advanced: edit as code, top right of the card list, swaps the visual builder for the raw JSON. Handy for bulk edits and for copying a set of cards between widgets, and the only way to produce a configuration the visual builder would refuse.
The Icon box is free text and expects a lucide icon name in
kebab-case: help-circle, alert-triangle,
smile, bug, package,
credit-card, calendar, star,
zap. Case and punctuation are forgiven, so
HelpCircle also works, but a name outside the supported set
silently falls back to the generic message bubble. That is the usual reason
every card ends up with the same icon. Watch the live preview as you type:
if the icon does not change, the name is not recognised.
The editor blocks saving a form card with zero fields. But if one reaches
the widget anyway (for example through the Advanced: edit as code
JSON box), the card silently behaves as a chat card and sends
its own title as a message. Similarly, a card with no title is dropped
entirely when the configuration is served.
Each field row in the editor has four controls: the label the
visitor reads, the type, a Required toggle,
and the key box on the right (that is maps_to, covered below).
| Dashboard label | Stored as | What the visitor sees |
|---|---|---|
| Short text | text | One-line input, with your placeholder if you set one. |
| Long text | textarea | Four-row resizable box. |
email | Email input. The only type whose format is validated. | |
| Dropdown | select | A select built from your comma-separated options, with a leading "Select…". |
| Single choice (radio) | radio | A vertical radio list built from your options. |
| Checkbox (yes/no) | checkbox | One tick box. Required means it must be ticked. |
| Smiley rating (1-5) | rating | Five faces, stored as "1" to "5". |
| NPS (0-10) | nps | A 0 to 10 row labelled "Not at all likely" and "Extremely likely". |
| Numeric scale | scale | A numeric row with the min and max you set, defaulting to 1 and 5. No captions. |
| Priority (low/normal/high/urgent) | priority | Four buttons: Low, Normal, High, Urgent, each with a colour dot. |
maps_to
Every field gets a random internal id such as f-a3k9, and raw
answers are stored under those ids. When answers are forwarded to a
destination they need a meaningful key instead: a ticket needs to know which
answer is the subject, GHL needs to know which is the email address. That is
what the key box (maps_to) sets.
maps_to, that is used.email, phone, first_name, last_name or name, that is used. A field labelled simply "Email" needs no maps_to.email.
A leading contact. or ticket. is accepted and
stripped automatically, so ticket.subject and
subject mean the same thing. The stock templates ship the
prefixed form.
| Destination | Keys it reads |
|---|---|
| Support ticket | subject, description (or body), priority; requester email from email, requester_email or visitor_email; requester name from name, requester_name, visitor_name or first_name |
| Feedback | message, description, comment or title for the comment; type, severity; plus the email and name keys above. The score is taken from the rating, NPS or scale field automatically. |
| GHL contact | email, phone, first_name, last_name, name, tag. Anything else is attached as a contact note. |
| Stripe lookups | email |
| Knowledge-base search | query |
The What should happen with this form? dropdown picks a single optional destination for a copy of the answers. "Just collect the answers" is a perfectly good choice; everything still lands in your Inbox.
Whatever the "if it is not connected" column says, the answers are already saved on Assistable's side before the forward is even attempted.
| Destination | What it does | What must be connected | If it is NOT connected |
|---|---|---|---|
| Just collect the answers | Saves the answers and opens a conversation in your Inbox. | Nothing. | Nothing to break. |
create_ticketcreate_support_ticket |
Creates a support ticket and routes it. Both names behave identically. Routing order is Zendesk, then Gleap, then the Assistable inbox; the first connected one wins. | Nothing for the in-app route. Zendesk or Gleap only if you want the ticket to appear there. | Falls down the chain to the Assistable inbox, which always works. A Zendesk API failure falls back silently to Gleap and then in-app, so a ticket may exist somewhere other than where you expected. Check the ticket's destination before concluding it vanished. |
submit_feedback |
Saves a feedback record (kind, rating, scale, comment). If Gleap is connected it is mirrored there in the background. | Nothing. Gleap optional. | The feedback is still saved. A failed Gleap mirror is recorded against the record and logged; the visitor never sees an error. |
ghl_contact |
Creates or updates a GoHighLevel contact from the email, phone and name fields, adds the configured tag, and attaches any remaining answers as a contact note. | A GHL connection on the subaccount, at least one field mapped to email or phone, and the tag your GHL workflow triggers on. |
Fails with ghl_not_connected, or email_or_phone_required if nothing is mapped to email or phone. If the contact is created but the tag fails you get ghl_tag_failed: the contact exists but your automation never fired. Answers are saved either way. |
webhook |
POSTs the mapped answers to the URL on the card, as {source, subAccountId, submittedAt, fields}. |
A public URL on port 80 or 443 that returns 2xx within 8 seconds and does not redirect. http:// is accepted as well as https://. Private and internal addresses are blocked. |
One attempt, no retries. The failure is logged and reported as not delivered; the answers are still saved. For guaranteed delivery use a workspace webhook subscription instead. |
search_kb |
Searches the knowledge bases attached to the widget's assistant. A lookup: nothing is stored. | An assistant with at least one knowledge base. | Returns nothing. Caveat: the form screen does not display lookup results, so use a chat card for knowledge-base questions instead. |
get_subscription_status |
Looks up the Stripe subscription for a given email. A lookup. | A Stripe integration on the subaccount, and a field mapped to email. |
Fails with stripe_not_connected or subscription_not_found and the visitor sees a generic error. Same display caveat as above. |
list_invoices |
Lists Stripe invoices for a given email. A lookup. | As above. | As above. |
ghl_contact writes into a live customer CRM: it creates or
updates a real contact and adds a tag that can trigger real automations
(emails, texts, pipeline moves). Test it against a throwaway location, never
against production.
"Create support ticket (Zendesk)" does not require Zendesk; it routes to whichever helpdesk is connected and falls back to the Assistable inbox when none is. Likewise "Submit feedback (Gleap)" saves feedback locally first and only mirrors to Gleap if Gleap happens to be connected.
In the JSON that comes back from actions/submit these are the
ok and delivered fields, with
delivery_error alongside them when a forward failed.
ok) means the answers are kept. This is what decides whether the visitor sees the success screen.delivered) means the forward you asked for actually happened. When it is false, delivery_error carries the reason.They are separate so that a dead destination never makes a visitor retype answers you already have. The flip side is that a failed forward is invisible to the visitor. When someone reports "the form worked but nothing arrived", the delivered flag in the submit response is the first thing to check. The three lookups are the exception: they store nothing, so when they fail the visitor sees an error.
This is the submission from the video above, read back from the database. The widget had no Zendesk and no active Gleap integration, which is the most common starting position.
SELECT _id, "actionId", "actionTitle", tool, "contactName", "contactEmail",
"contactId", "conversationId", "createdAt"
FROM "widgetFormSubmission"
WHERE "widgetId" = 'cmqj7kubf0001rua7841r0345'
ORDER BY "createdAt" DESC LIMIT 1;
_id | cmsplrohu000alsrul13xlkrw
actionId | bug-vsh3mt
actionTitle | Report a bug
tool | create_support_ticket
contactName | Dana Whitfield
contactEmail | dana.whitfield@northwind-demo.test
contactId | cmspi54l00000e0rutazat78n
conversationId | cmsplrohb0008lsrui8gfrygg
createdAt | 2026-08-12 04:42:13.314
The raw answers live on the same row, keyed by field id:
{
"f-19tc": "Dana Whitfield",
"f-a36o": "dana.whitfield@northwind-demo.test",
"subject": "Checkout fails on the final step",
"priority": "high",
"description": "Adding a cordless drill to the basket works, but pressing
Pay returns 'order could not be created'. Reproduced in
Chrome and Safari."
}
maps_to is for
subject, priority and description are
readable because they came from a preset. The name and email fields were
added by hand and kept their random ids f-19tc and
f-a36o. That is fine here, because both have
maps_to set (contact.name and
contact.email), so the ticket still knows which is which. Had
maps_to been left blank on a label the system cannot guess,
that answer would have reached the ticket as f-19tc and been
ignored.
_id | cmsplv0d6000flsrufcv2r245
subject | Checkout fails on the final step
priority | high
status | open
source | home-action
requesterName | Dana Whitfield
requesterEmail | dana.whitfield@northwind-demo.test
destination | in-app
externalId |
contactId | cmspi54l00000e0rutazat78n
conversationId | cmsplv0ca000clsruej7v1bw4
submissionId | cmsplv0co000elsrusu9b1qia
destination = in-app is the fallback working exactly as designed:
no helpdesk was connected, so the ticket lives in Assistable and the team
works it from the Inbox. With Zendesk or Gleap connected this would read
zendesk or gleap and the row would also carry the
external ticket id and URL.
_id | cmsplrohb0008lsrui8gfrygg
lastChannel | Live_Chat
lastSubjectLine | Checkout fails on the final step
contactEmail | dana.whitfield@northwind-demo.test
previewText | Adding a cordless drill to the basket works, but pressing
| Pay returns 'order could not be created'. Reproduced in
| Chrome and Safari.
One exception: a submission that is nothing but a rating, for example
a bare NPS score with no comment, does not open a conversation. Those live in
the submission and feedback records only. Ticket destinations are exempt from
the exception: create_ticket and create_support_ticket
always open a conversation, even from a bare rating. If you want NPS responses
visible to your team, add an optional comment field to the survey.
Tabs & content, the Shown/Hidden switch on each card · visible_tabs
There are five tabs: Home, Messages, News, Roadmap and Help. Each card in the editor has a Shown/Hidden switch, and the counter at the top reads "N of 5 shown". You cannot hide the last remaining tab.
visible_tabs is ignored; tabs always render Home, Messages, News, Roadmap, Help.Tabs & content › Messages · visibility only, there is nothing to edit
Messages lists the visitor's past conversations with this assistant, newest first, up to 30. Each row shows who they were talking to, a preview, a timestamp, and a status chip: AI, LIVE when a human has taken over, or ENDED when the conversation was resolved.
If the sessions request fails, the tab shows "No messages
yet" rather than an error. History is also keyed on a per-browser
visitor id, so clearing site data loses it unless the visitor's email was
captured. If a customer says their visitors cannot see past chats, check the
network tab for the /api/v1/widget/sessions call before
assuming the history is gone.
Tabs & content › News · news.heading, news.empty_message, news.items
A changelog for your visitors. Add items by hand, or paste an RSS feed URL and press Fetch to import them. Each item has a Title, Summary, Category, Image URL, Link and Published date; only the title is required.
news renders as "News" and changelog
as "Changelogs". Anything else is lower-cased with only its first letter
capitalised, so "Product Updates" shows as "Product updates".
published_at is used only to sort the list newest first. It is
not rendered anywhere in the widget, so "the date is missing from my update"
is not a bug and there is no format to configure. Items with no date sort to
the bottom. An item with no category is treated as news.
Tabs & content › Roadmap · roadmap.heading, roadmap.empty_message, roadmap.items
What you are building. Each item has a Title, Description and Status: Considering, Planned, In progress or Shipped. Visitors get two dropdowns: sort by Most votes or Newest, and filter by status.
The up-chevron beside each vote count looks clickable but is not wired to anything in the widget: it is a plain number with an arrow above it. Visitors cannot vote. Expect "voting is broken" reports and answer them with this. Vote counts only change if something else writes them.
The status dropdown only lists statuses that actually exist in your data, so "Shipped" does not appear as a filter until you have shipped something.
Tabs & content › Help · help.heading, help.empty_message, help.search_placeholder, help.articles
Help does two things at once. It shows the articles you pin (Title, Description, Article URL, optional Section), grouped by section. And, if the widget has an assistant, it searches your knowledge base live as the visitor types, showing matches under "From the knowledge base" that expand in place.
You can add articles by hand, paste one or more URLs and press Fetch to scrape their titles and descriptions, or Upload CSV / JSON.
Behavior › Pre-chat form, plus the name / email / phone checkboxes · pre_chat.form_first, pre_chat.fields
With this on, the visitor must give their details before they can send a first message. It is skipped automatically for visitors whose details you already have.
Every field you tick is mandatory. There is no way to ask for a phone number optionally: ticking Phone makes it required. Ticking fewer boxes is the only way to reduce friction.
There is no skip button. The only ways past the form are a valid submit, or already having contact details stored in the browser.
The name / email / phone checkboxes are read from the server and
win over the embed snippet. Only when the saved list is
empty, or contains nothing the widget recognises, does it fall back to
data-pre-chat-fields on the snippet, and failing that to
name and email.
This used to work the other way round: bundles built before this was joined
up ignored the dashboard checkboxes entirely. A site pinned to an old copy
of chat-widget-v2.js will still need
data-pre-chat-fields="name,email,phone" on the snippet.
Appearance › Identity · display_name, brand_name, header_subline, show_online_indicator
Back arrow, avatar, then up to three lines of text: the display name, the company name, and either the header subline or the online row.
The online row needs something to say: it renders when a human has taken over ("Live agent connected") or when you have set a header subline. With neither, turning the indicator on changes nothing.
Assistant › Intro Message and Suggested Prompts · intro_message, prompts
The intro message is the first bubble in every new conversation. Below it, all your suggested prompts render as chips until the visitor sends their first message, then they disappear for good.
Visitor messages are right-aligned in the brand colour; assistant messages are left-aligned in grey with the AI avatar. Assistant text is rendered as Markdown (bold, lists, links, tables, code), visitor text is not.
Markdown headings are flattened: #, ## and
### all render as ordinary bold text, not at heading size. If
an assistant's answers look unstructured, that is why.
Placeholder "Message…", or "Conversation ended" once a conversation is resolved. Alongside the send button there is a paperclip for attachments and, in browsers that support it, a microphone for dictation.
Attachments have a quirk: the paperclip accepts images, PDFs, Word, text, CSV, spreadsheets and JSON, but drag-and-drop and paste accept images only. "I can attach a PDF with the clip but not by dragging it" is expected.
Assistant › Fallback Message · fallback_message
Shown as an assistant bubble when the AI cannot be reached. Note that the visitor sees two things in that situation: your fallback message, and a separate built-in card offering to take their email. The second one is not configurable.
Behavior › AI Disclaimer and Appearance › Company name · ai_disclaimer, show_branding, brand_name
The disclaimer is a small centred line under the composer, maximum 120 characters. The "Powered by" line sits below it and needs both the branding switch and a company name; with the name blank it silently renders nothing.
Behavior › Notification sound · notification_sound
Plays a short chime when a reply arrives while the widget is closed.
Ping, Pop, Subtle, Futuristic, Attention and Bottle all save correctly, but the widget ignores the choice and plays one hard-coded tone. Only the on/off switch has an effect today.
A one-line strip at the top of the message area reading "See you on your appointment at Monday, Aug 17 at 3:30 PM". It appears when the widget has been given an upcoming appointment with both a start time and a timezone. A missing timezone hides it entirely, with no local-time fallback, which is the usual reason it does not show. The wording is fixed and English-only.
Behavior › Human handoff · handoff_mode, office_hours
The dashboard stores handoff as an on/off switch plus a mode. The
disabled value below is what the configuration endpoint reports
when the switch is off; it is not a third choice in the editor.
| Setting | Reported as | What the visitor experiences |
|---|---|---|
| Handoff off | disabled | No handoff surface at all. The AI handles every conversation. |
| Show it | support | A "Talk to a human" chip appears in an empty chat, and when someone takes over the visitor sees a banner naming them. Intercom-style. |
| Stay silent | transparent | Agent replies arrive as ordinary assistant bubbles. The visitor is never told a human joined, and there is no handoff button. Chatwoot-style. |
The Notify your team on handoff row shows whether Slack is connected. It is a status display only; connect Slack from Portal › Integrations.
Behavior › Voice calling · voice_enabled
With voice on, the chat header gains a Chat / Voice switch. Voice puts the visitor on a live call with the same assistant, seeded with the last few turns of the chat so the agent already knows the context.
| State | On screen |
|---|---|
| Idle | "Tap to start a voice call", one Start voice call button. |
| Connecting / ringing | "Connecting…" then "Ringing…", with a mute button and a red hang-up button. |
| Live | The call duration as mm:ss, replacing the status text. Mute and hang up. |
| Ended | "Call ended", and the button becomes Call again. |
| Error | "Couldn't connect", plus a red box with the reason (blocked microphone, no microphone, voice unavailable). |
The switch appears when the Behavior toggle is on and the assigned assistant has a voice agent configured. The editor shows an amber warning on the toggle when the second condition is missing. The embed snippet cannot turn voice on by itself; only the server can.
The toggle is on by default. A widget whose Voice calling switch has never been touched counts as on, so the moment its assistant gains a voice agent the Chat / Voice switch appears without anyone having enabled it. If you do not want voice, turn the toggle off explicitly rather than assuming it starts off.
Switching back to Chat during a call hangs up. The call is not put on hold; it is torn down. Tell visitors to use the red hang-up button, not the Chat tab.
Integrations & tools · these toggles belong to the assistant, not the widget
When the assistant calls a tool, the result renders as a rich card in the conversation rather than as text. Tickets, feedback, Stripe subscriptions and invoices, bookings and availability, product carousels, orders, refunds, discounts and checkout all have their own card.
create_support_ticket result. The card is rendered by the
widget; the model response behind it is stubbed (see
section 27).
You cannot hide tool result cards. A separate flag,
show_tool_activity, governs the transient grey "Using create
support ticket…" line while a tool runs. It is off by default and the
configuration endpoint never sends it, so in practice that line does not
appear at all. The finished card always renders and cannot be suppressed.
Customers who ask to "hide the tool stuff" usually mean the cards.
An unrecognised tool renders nothing at all. If a custom tool has no card of its own, the visitor sees the assistant's text and no trace that anything ran. A failed tool, by contrast, always shows a red "Error" card.
add data-bug-report="true" to the snippet
A one-tap "Report a problem" entry on Home that takes a screenshot of the page automatically, lets the visitor describe what went wrong, and sends both.
Appearance › Theme › Brand color · color
Used for the launcher, the send button, visitor message bubbles, the active tab, links inside assistant messages and a faint tint on the Home background. Pick a hex value; the swatches are shortcuts.
Use a six-digit hex. Some effects (the voice call's pulse ring, the shadow
under the launcher) append transparency to the value and will silently look
wrong with a named colour or an rgb() string.
Appearance › Theme › Mode · dark_mode
A single fixed choice, not a follow-the-visitor's-system option.
Look at the action cards above: they keep a light background in dark mode because their fill is hard-coded rather than themed. The chat composer box stays light for the same reason. Test dark mode before enabling it for customers.
Appearance › Theme › Corner style · Rounded / Soft / Sharp · theme
Controls the panel's corner radius: 20, 14 and 6 pixels respectively.
Appearance › Identity and Icons & avatars
| Field | Key | Where it shows |
|---|---|---|
| Display name | display_name | The bold name in the chat header and on the teaser. |
| Company name | brand_name | The second line in the chat header, and the "Powered by" line. |
| Header subline | header_subline | Small text under the name in the chat header. Maximum 60 characters. |
| Header & bot avatar | logo_url | Header, AI message bubbles, Home, and the closed launcher. |
| AI message avatar | ai_avatar_url | Overrides the above on AI bubbles only, so the bot can have a different face from the brand. Note the label is misspelled "AI messae avatar" in the dashboard. |
| Fallback icon | icon no effect | Used by the older Floating Orb widget type. The chat widget never reads it. |
| Attribute | Default | What it does |
|---|---|---|
data-bug-report="true" | off | Shows the "Report a problem" card on Home. No dashboard equivalent. |
data-pre-chat-fields="name,email,phone" | name,email | Which pre-chat fields render when the dashboard checkboxes are empty. A saved dashboard list overrides this attribute. |
data-pre-chat-heading, data-pre-chat-subheading | "Before we start" / "Quick info so we can follow up." | The pre-chat form's two lines of copy. |
data-show-prompt="false" | on | A hard opt-out for the teaser. Unlike every other setting, the server cannot override this one back on. |
data-enable-memory="false" | on | Stops the widget remembering the visitor between visits. |
data-voice-endpoint | {api url}/v3/create-web-call | Points voice calls at a different endpoint. Rarely needed, and ignored once a chat session exists: a call started from inside an existing conversation always uses the default. |
These save correctly and appear in the configuration, but nothing in the running widget reads them. They are listed here so you do not spend an afternoon debugging one.
| Setting | Where it appears | Reality |
|---|---|---|
Persist conversationpersist_history | Behavior | Chat history is kept regardless. Turning it off does not stop it. |
Notification sound choicenotification_sound_kind | Behavior, six buttons | One hard-coded chime plays whatever you pick. The on/off switch does work. |
Fallback iconicon | Appearance | Belongs to the Floating Orb widget type, not this one. |
prompts_source | not in the UI | Reported by the API so you can tell whether prompts came from the widget or the assistant. The widget ignores it. |
The widget contains a "How was your experience?" star-rating popup that is meant to appear when a visitor closes the panel after a conversation. In the current build nothing triggers it: the only close control available inside a conversation is the launcher, and that path skips the popup entirely. Do not promise customers a post-chat rating prompt. Use an NPS home-action card instead, which does work (see section 8).
Behavior › Allowed Domains · one per line · allowed_domains
| Rule | Behaviour |
|---|---|
| Empty list | Allows every domain. This is the default for a new widget, and it means an unconfigured widget can be embedded anywhere. |
| Exact hostname | example.com matches only example.com. |
| Wildcard | *.example.com matches app.example.com and deeper, but not the bare example.com. List both if you need both. |
| A blocked origin | The API answers 403 origin_not_allowed. |
Not an error message. The configuration call is not gated, so the widget loads and looks completely normal; every message then fails and the visitor sees your fallback message. A misconfigured allowlist therefore presents as "the bot stopped answering", not as "the bot is blocked". Check the network tab for a 403 before chasing the AI.
| Setting | Where | What it does |
|---|---|---|
Sync conversations to GoHighLevelghlSync |
Behavior | Mirrors widget conversations into the connected GHL location so they appear in the CRM's inbox. Off by default. Never shown in the public configuration. |
TicketingticketsEnabled |
No UI | Server-side switch, on unless explicitly set to false, that allows this widget to create support tickets. There is no dashboard control for it today. |
| Rate limits | Not configurable | Roughly 30 chat messages per minute per visitor, plus per-IP and per-widget ceilings. Exceeding them returns 429. |
Every key the widget configuration returns, its default when unset, and where you change it. no effect marks keys the running widget ignores.
| Key | Default | Dashboard location | Effect |
|---|---|---|---|
color | #000000 | Appearance › Brand color | Launcher, buttons, visitor bubbles, active tab. |
display_name | the widget's name | Appearance › Display name | Bold name in the header and teaser. |
brand_name | the subaccount name | Appearance › Company name | Second header line; required for the "Powered by" footer. |
logo_url | none | Appearance › Header & bot avatar | Header, AI bubbles, Home, closed launcher. |
icon no effect | message-circle | Appearance › Fallback icon | Floating Orb only. |
button_icon | chat | Appearance › Launcher icon | Launcher glyph. One of chat, message, question, headset, sparkles. |
ai_avatar_url | none | Appearance › AI messae avatar | Overrides the logo on AI bubbles. |
teaser_color | #1c1c1e | Appearance › Teaser bubble color | Teaser background; text contrast auto-adjusts. |
theme | rounded | Appearance › Corner style | Panel corner radius: rounded 20px, soft 14px, sharp 6px. |
dark_mode | false | Appearance › Mode | Dark palette. |
auto_open | false | Appearance › Auto-open on page load | Panel opens on load. |
notification_sound | off | Behavior › Notification sound | Chime on a new reply while closed. |
notification_sound_kind no effect | none | Behavior, six buttons | Choice is ignored; one fixed tone. |
| Key | Default | Dashboard location | Effect |
|---|---|---|---|
intro_message | from the assistant | Assistant › Intro Message | First bubble of a new conversation. |
idle_message | from the assistant | Assistant › Popup Message | The teaser's second line. |
menu_header | "How can we help?" | Tabs & content › Home › Header | Home heading. |
menu_subheader | "We typically reply in a few minutes." | Tabs & content › Home › Sub-header | Home sub-heading. |
fallback_message | a built-in apology | Assistant › Fallback Message | Shown when the AI cannot be reached. |
teaser_greeting | from the assistant | Tabs & content › Launcher teaser › Greeting | Teaser headline, max 80 chars. |
teaser_suggestions | empty | Tabs & content › Launcher teaser › Suggestion chips | Up to 6 chips, one per line. |
prompts | from the assistant | Assistant › Suggested Prompts | Chips: 2 on Home (only when there are no action cards), all in an empty chat. |
prompts_source no effect | assistant | not in the UI | Tells you whether prompts came from the widget or the assistant. |
ai_disclaimer | none | Behavior › AI Disclaimer | Small line under the composer, max 120 chars. |
| Key | Default | Dashboard location | Effect |
|---|---|---|---|
visible_tabs | Home + Messages | Tabs & content, Shown switches | Which tabs appear. One tab means no nav bar. |
position | bottom-right | Appearance › Position | Which corner the widget sits in. |
launcher_size | md | Appearance › Launcher size | 48 / 60 / 72 pixels. |
header_subline | none | Appearance › Header subline | Small line in the chat header, max 60 chars. Suppressed by the online indicator. |
home_actions | from the template | Tabs & content › Home › Home action cards | The buttons on Home. |
pre_chat.form_first | false | Behavior › Pre-chat form | Gate the chat behind a contact form. |
pre_chat.fields | empty | Behavior, three checkboxes | Which pre-chat fields render. Overrides data-pre-chat-fields whenever it is non-empty. |
news.heading / .empty_message / .items | "News" / built-in copy / empty | Tabs & content › News | News tab title, empty state, list. Max 50 items. |
roadmap.heading / .empty_message / .items | "Roadmap" / built-in copy / empty | Tabs & content › Roadmap | Roadmap tab title, empty state, list. Max 50 items. |
help.heading / .empty_message / .search_placeholder / .articles | "Help center" / built-in copy / "Search articles…" / empty | Tabs & content › Help | Help tab title, empty state, search box, pinned articles. Max 50 items. |
show_prompt | true | Tabs & content › Launcher teaser › Show teaser bubble | Whether the teaser can show at all. |
show_branding | false | not exposed today | "Powered by {company name}" under the composer. |
show_online_indicator | false | Appearance › Online indicator | Green dot in the chat header. |
persist_history no effect | true | Behavior › Persist conversation | History is kept either way. |
| Key | Default | Dashboard location | Effect |
|---|---|---|---|
voice_enabled | on — an untouched toggle counts as on | Behavior › Voice calling | Chat / Voice switch. Only ever true when the assistant also has a voice agent. |
handoff_mode | disabled | Behavior › Human handoff | support, transparent or disabled. |
office_hours | none | Behavior › Office hours | Timezone, weekly ranges, offline heading and message, collect email. |
allowed_domains not in the public config | allow all | Behavior › Allowed Domains | Origin allowlist, enforced by the API. |
ghlSync not in the public config | off | Behavior › Sync conversations to GoHighLevel | Mirrors conversations into GHL. |
ticketsEnabled not in the public config | on | no UI | Allows this widget to create tickets. |
First step for almost anything: open browser dev tools, go to the Network tab, reproduce the problem, and read the failing request. The widget deliberately shows generic messages; the response body carries the real reason.
| Symptom | Likely cause | What to check and fix |
|---|---|---|
| Nothing renders at all | The configuration call failed, or the identity check rejected it. | Console will show widget_config_http_error, widget_config_timeout or widget_config_network_error. Confirm the widget is not archived, that it has an assistant assigned, and that data-api-url is reachable from the visitor's network. |
| The widget loads but every message fails | The embedding domain is not on the allowed list, or the rate limit was hit. | Look for 403 origin_not_allowed or 429 on the chat request. Remember a wildcard *.example.com does not cover the bare example.com. An empty allowlist allows everything, so this only bites once someone has filled it in. |
| Edits in the dashboard do not show on the site | Caching, or an old widget script on the page. | Wait a minute and hard-refresh: configuration is cached about 30 seconds in the browser and up to 5 minutes on the server. If a whole feature is missing rather than a setting, the page may be loading an old copy of the widget script. |
| The tabs are missing | Only one tab is visible, or the visitor is inside a conversation. | The nav bar hides itself when a single tab would show, and it is always hidden inside a conversation and the bug reporter. Turn on a second tab under Tabs & content. |
| All the action card icons look the same | The icon names are not recognised, so every card falls back to the generic bubble. | The Icon box is free text with no validation. Use lucide names in kebab-case: bug, lightbulb, smile, help-circle, alert-triangle, package, credit-card, calendar, star, zap. Watch the live preview: if the icon does not change as you type, the name is wrong. |
| An action card is missing from the widget | Not saved, dropped as invalid, or cached. | Did the save actually succeed? One broken card blocks the whole save. A card with no title is silently dropped when the configuration is served. Otherwise wait a minute and hard-refresh. |
| Suggested prompts vanished from Home | You added an action card. | Prompt chips only render on Home when there are no action cards at all, and only the first two of them. They still appear inside an empty chat. |
| The form says "Sorry, that didn't go through" | The server rejected the submission. | Read the response body of actions/submit. Missing required field: X means an empty required field or a stale config. action_not_form_mode or action_not_found means a cached config or a deleted card: wait a minute or re-save the widget. tool_not_allowed means the destination value is not one of the supported ones, usually a typo made in the JSON editor. |
| The form succeeded but nothing reached the other system | Saved and delivered are separate. The forward failed after the save. | Read the delivery error code. GHL: ghl_not_connected, email_or_phone_required (nothing mapped to email or phone), ghl_tag_failed (contact created, tag not added, so the workflow never fired). Webhook: one attempt, no retries, must be public HTTPS returning 2xx within 8 seconds with no redirect. |
| A ticket went missing, or landed in the wrong place | Routing precedence and silent fallback. | The order is Zendesk, then Gleap, then the Assistable inbox, and a Zendesk failure falls back silently. Check the ticket's destination. The dropdown label "(Zendesk)" is misleading: the destination works with nothing connected. |
| A second report from the same visitor never appeared | Deduplication. | There is one open ticket per conversation. A repeat request updates the existing ticket and appends to the conversation instead of filing a duplicate. A new ticket only opens once the previous one is resolved. |
| NPS or rating responses seem to vanish | A submission that is only a rating creates no Inbox conversation, unless its destination is a support ticket. | Add an optional comment field so submissions create Inbox threads, or subscribe to the widget.form.submitted webhook event. |
| Visitors cannot see their past conversations | A failed sessions lookup looks identical to an empty history. | Check /api/v1/widget/sessions in the network tab. History is keyed on a per-browser visitor id, so clearing site data loses it unless an email was captured. Only the 30 most recent are returned. |
| "Talk to a human" is not showing | Handoff is off, is set to Stay silent, or the visitor already sent a message. | Handoff must be on with mode Show it, and the chip only appears before the visitor's first message. Outside office hours it is replaced by the email capture card. |
| The widget says we are offline during working hours | Wrong timezone, or a day left unticked. | Office hours use the timezone in the editor, not the visitor's. A day with no ticked range counts as closed. Note the rules fail open, so an invalid timezone shows you as available rather than offline. |
| The Chat / Voice switch is not there | Voice is off, or the assistant has no voice agent. | Both must be true. The editor shows an amber warning on the Voice calling toggle when the assistant is missing a voice agent. The embed snippet cannot enable voice. |
| A call drops when the visitor goes back to Chat | Expected. The call is torn down, not held. | Tell visitors to use the red hang-up button. |
| Roadmap voting does not work | It is not wired up. | The arrow beside each count is decoration. Visitors cannot vote from the widget. |
| The published date is missing from News items | It is never displayed. | The date is used only to sort newest first. Put it in the summary if visitors need to see it. |
| Help search returns nothing | Fewer than two characters, or the knowledge-base call is blocked. | Knowledge-base search needs two characters and 300ms of quiet. It is the only widget call on the /api/v2/ path, so a proxy rule that allows only /api/v1/ breaks help search and nothing else. |
| Dark mode looks half-finished | Known limitation. | Home action cards and the chat composer keep light backgrounds in dark mode. Test before enabling. |
| The logo disappears when the chat opens | Expected. | The launcher only shows the logo while closed; open, it shows a chevron. |
| The teaser keeps coming back | Expected. | Dismissal is stored per browser tab session. To hide it permanently, clear the teaser greeting or add data-show-prompt="false" to the snippet. |
Every screenshot and recording here was captured from a running stack: the Assistable v2 dashboard, the chat widget on an ordinary demo web page, and the API, all against a restored copy of production data. Nothing is a mock-up.
The first edition of this guide was written from a source reading that was never independently checked, and it shipped with errors, including an install snippet pointing at a host that serves no widget bundle. Every claim has since been re-verified against the code, the live configuration endpoint and the production database, and the corrections are folded in above. Where something could not be verified it now says so.