Fellix

Web chat — developer reference

Everything the embed snippet understands: the attributes you can put on the tag, the hooks it offers your own page, and the methods it hangs on window.fellixWidget.

Written for whoever pastes the script. Everything on this page is read by widget.js on your site — none of it is a setting saved in Fellix. The settings that are saved (colour, greeting, business hours, who may embed it) live in the dashboard, under the channel.

Install

One line of HTML, once per page that should show the chat. Put it before the closing </body> tag. Your channel's own line — with its key already in it — is in the dashboard under Web Chat Channels → your channel → Overview.

<script src="https://widget.fellix.ai/widget.js" data-key="wc_pk_…" async></script>

async is deliberate: nothing on your page waits for us. The script draws a launcher button and nothing else. The conversation itself lives in an iframe on Fellix's own origin, and that iframe is not created until the visitor opens the panel — so a visitor who never clicks pays for one small script and no more.

The boundary is the point. None of our React, CSS or network traffic runs in your document. Your stylesheet cannot reach into the panel, and ours cannot leak out onto your page.

Snippet attributes

All optional except data-key. Each is read once, from the script tag itself, at the moment the script runs — changing an attribute later has no effect, because by then the tag is no longer identifiable.

data-key required

This channel's public embed key. Without it the widget does not start: it logs [fellix] the embed snippet is missing its data-key attribute to the console and returns, drawing nothing. If you pasted the snippet and no launcher appeared, that console line is the first place to look.

The key is publishable by design — it identifies the channel, it does not authorise anything. Which websites may use it is controlled separately, under the channel's Security settings.

data-lang

What language the widget speaks.

absent, or page
Follow the page's own <html lang>, falling back to the visitor's browser when the page declares none. This is the default, and usually the right answer: the widget is furniture on your page, and an English-only site should not show a Turkish composer under English content just because the visitor's browser is set to Turkish.
browser
Follow the visitor's own browser preference instead of the page.
a language tag — tr, de, pt-BR
Pin it, whatever the page and the visitor say.

Only the first two letters are used to pick the wording, so pt-BR and pt reach the same translation. The widget ships English, Turkish, German, Spanish, French, Arabic and Russian; anything else falls back to English. Arabic, Hebrew, Persian and Urdu also flip the panel to right-to-left.

data-theme

Whether the panel is painted light or dark.

absent, or page
Follow the page. We read the computed color-scheme of your <html> element — not a class name, because .dark is Tailwind's convention and data-bs-theme is Bootstrap's and neither is recognisable on a stranger's site. color-scheme is the one answer every framework produces, because the browser itself needs it to paint the canvas and the form controls.
os
Ignore the page and follow the visitor's own system setting.
light / dark
Pin it.

The channel outranks this. If the channel's Panel theme is set to Light or Dark in the dashboard, that wins and data-theme is not consulted at all — a page is not entitled to overrule the brand it embedded. This attribute only does something while Panel theme is on System.

data-position

Which corner the launcher sits in.

absent
The channel's Bubble position setting decides, as it does for every site the key is pasted into. That setting has a Follow the visitor's language switch of its own, so leaving this attribute off does not necessarily mean a fixed corner — it means whichever of the two the dashboard is set to give.
left / right
Pin it for this site.
auto
Follow the widget's own language: right on a left-to-right page, left on a right-to-left one. What a site that mirrors itself for Arabic wants.

Unlike data-theme, the page wins here. The setting is one answer for everywhere the key is used; this attribute is one site talking about its own corners, where a cookie bar or a back-to-top button may already be.

data-label

Replaces the text a screen reader announces for the launcher button. Defaults to a translated “Chat with us”. Set it when “chat” is not what the button means on your site — “Book a table”, “Ask about your order”. It is an accessible name, not visible copy; the visible label is the channel's Bubble label setting.

data-jwt

A signed identity token for the person reading the page, when your backend renders the snippet server-side and already knows who they are. A single-page application that learns who its user is only after a login calls fellixWidget.identify() instead. See Signed identity.

data-panel

The origin the panel iframe is loaded from. Defaults to the origin of the script's own src, which is correct for every ordinary installation — you will not normally set this. It exists for environments that serve widget.js from a different host than the panel, such as a reverse proxy in front of both.

Leave it off rather than empty. data-panel="" is not the same as omitting it: an empty value is still a value, so the fallback to the script's own origin never happens and the panel is loaded from nowhere. Templating engines that emit every attribute whether or not they have something to put in it are the usual way this happens.

Opening it from your own page

Two ways in that need no JavaScript of yours. Both work alongside the launcher, and both still work when the launcher is hidden — the channel's Use my own button setting turns the bubble off entirely and leaves these as the way in.

[data-fellix-open]

Put the attribute on any element — a button, a link, a menu item — and clicking it opens the panel. An attribute rather than an id or a class, because an id of ours in your stylesheet's way is a collision waiting to happen, and this reads as intent on your own button.

<button data-fellix-open>Talk to us</button>

The click is delegated from the document, so an opener your app renders later — after a route change, inside a modal — works without re-registering anything. On an <a> the default navigation is prevented, so you can write a real href as a fallback for the JS-disabled case.

The #fellix-chat fragment

Arriving on a page whose URL ends in #fellix-chat opens the panel. This is how the link in a reply notification brings someone back to the conversation. The fragment is taken back off the address immediately, before anything can write it to history or pass it on as a referrer, so the visitor is left on the page they came to read with the panel on top of it.

JavaScript API

Available on window.fellixWidget as soon as the snippet has run. Because the script is async, that is not necessarily before your own code runs — guard with window.fellixWidget?. rather than assuming it is there.

fellixWidget.identify({ jwt, attributes })

window.fellixWidget?.identify({
  jwt: '<token your backend signed>',
  attributes: { plan: 'pro', seats: 12, trial: false },
})

Hands over the signed identity token, and optionally what your page claims about the visitor. Call it after login in an app that learns who its user is late. Passing jwt: null hands over claimed attributes without an identity.

Flat scalars only. Attribute values that are not a string, a number or a boolean are dropped before anything is sent — a nested object or an array does not arrive truncated, it does not arrive. Flatten it yourself if you need it: { 'plan.name': 'pro' }, not { plan: { name: 'pro' } }.

fellixWidget.update({ … })

Adds or replaces claimed attributes without touching the identity token. Merges — keys you do not mention keep their previous value. Same flat-scalars rule as identify.

fellixWidget.track(name)

window.fellixWidget?.track('checkout_started')

Names a moment on your page. It reaches the channel's targeting rules — so a proactive message can be written to fire on it — and lands in the organization's analytics. An empty or whitespace-only name is ignored.

fellixWidget.open()

Opens the panel, as though the visitor had pressed the launcher. Also dismisses the proactive greeting bubble if one is showing.

fellixWidget.close()

Closes the panel. The conversation is not ended and nothing is lost.

fellixWidget.logout()

Drops the identity, the claimed attributes and the visitor's thread. Call it on sign-out — otherwise the next person at that computer opens the previous one's conversation.

fellixWidget.destroy()

Removes the widget from the page entirely, thread included, and deletes window.fellixWidget. For a page that stops being entitled to the widget without navigating away — a single-page app where signing out only unmounts a component.

It performs a logout first and removes the iframe a moment later rather than in the same tick. That delay is load-bearing: the logout is a message to the panel, and the panel is the half that clears the stored thread. An iframe removed immediately never receives it.

Signed identity

Anyone can open a chat anonymously; that is the normal case and needs nothing from you. Signed identity is for when the conversation should be attached to a person your system has already authenticated, and when the visitor's browser must not be able to claim to be someone else.

Your backend signs a token with the channel's signing secret — found under the channel's Security settings — and you hand it over in one of two ways:

The secret is a secret. It signs identities, so it belongs on your server and only there. Anything you put in front of a browser — including data-key — is public.

Attributes passed alongside the token are claims your page makes, not verified facts. They are useful for targeting and for what an agent sees; do not use them for anything the visitor should not be able to influence.

What it touches on your page

The complete footprint, so you can predict it:

  • one <button> appended to <body>, marked data-fellix-launcher, at a very high z-index;
  • one <iframe>, created on first open, pointing at the panel's own origin;
  • a click listener on document — for [data-fellix-open] — plus resize and message listeners on window;
  • three storage keys on your origin, all prefixed fellix.webchat.: muted and teased in localStorage — the visitor's sound preference, and the fact that the proactive greeting has already been shown once — and open in sessionStorage, so a panel left open survives the visitor clicking through your site but not their next visit. The conversation itself is not here: it lives in the panel's own storage, on Fellix's origin. Storage being denied outright costs a remembered preference and nothing more;
  • the page title, briefly, when an unread reply arrives while the tab is in the background.

Not touched: your stylesheet, your globals beyond window.fellixWidget, your cookies, and the document's own scroll position. destroy() reverses all of it.