Live Chat
Put the Brandfine Live Chat bubble on your site — fetch the bootstrap server-side, inject the widget client-side, zero secrets in the browser.
bf.liveChat renders the chat widget for this workspace. It's a
server-half / client-half pair, same shape as
analytics: your server fetches the bootstrap
with the secret workspace key, your client injects the widget with
only the publishable key — the browser never sees a secret.
// Server half (RSC / build step) — secret key stays here
import { createBrandfineClient } from '@brandfine/client'
const bf = createBrandfineClient({
baseUrl: process.env.BRANDFINE_API_URL!,
apiKey: process.env.BRANDFINE_API_KEY!, // broad key — server-only
})
const config = await bf.liveChat.getConfig()
// → pass `config` to your client component as a prop// Client half — no secrets, pure DOM injection
'use client'
import { useEffect } from 'react'
import { createBrandfineClient, type LiveChatBootstrap } from '@brandfine/client'
export function LiveChatClient({ config }: { config: LiveChatBootstrap | null }) {
useEffect(() => {
if (!config) return
const bf = createBrandfineClient({
baseUrl: process.env.NEXT_PUBLIC_BRANDFINE_API_URL!,
apiKey: 'unused', // install() never sends it
})
void bf.liveChat.install({ config })
}, [config])
return null
}Drop the server component once in your root layout. After that, everything is controlled from the CMS — activating, deactivating, greeting, theme all take effect on the visitor's next page load, with no redeploy of your site. The widget's first runtime call re-checks the enabled state, so even static-export sites (where the server half runs at build time) follow the CMS toggle live.
The same applies to the workspace's AI assistant: enable it in the CMS and the widget needs nothing from you — assistant replies arrive like agent messages, automatically labeled AI assistant in the bubble.
API
getConfig() → Promise<LiveChatBootstrap>
Server-side only — calls
GET /external/live-chat/bootstrap with the
broad workspace key. Resolves to:
{ enabled: false }
// or
{
enabled: true,
publishableKey: string, // brandfine_pk_… — safe in public HTML
greeting: string | null,
offlineMessage: string | null,
theme: Record<string, string> | null, // --bf-chat-* CSS vars
scriptPath: string, // widget bundle path, relative to baseUrl
}{ enabled: false } means the workspace hasn't activated Live Chat
(or toggled it off) — pass it through; install() no-ops.
install(opts) → Promise<LiveChatInstallResult>
Client-side half. Appends the widget host
(<div data-bf-live-chat data-publishable-key="…">) and the widget
<script> to the page. Idempotent via a marker attribute — safe
under StrictMode double-invokes and SPA re-mounts. Resolves to:
{ installed: true }
{ installed: false, reason: 'disabled' } // config.enabled === false
{ installed: false, reason: 'ssr' } // no document — call client-side
{ installed: false, reason: 'already-installed' } // prior call wonUnlike analytics.install(), the config option is required —
fetching here would need the broad key in the browser, which is
exactly what this design avoids.
Verified visitors (signed identity)
On pages where the visitor is already signed in, label the chat with
who they are — the inbox shows their name with a ✓ Verified
marker, and their conversation continues across devices and
sessions (same externalId = same person).
Identity is signed on your server — and needs zero extra
configuration: by default identityToken() derives the signing
secret from the API key your client is already constructed with
(the Brandfine API derives the same value from its stored copy).
No new env var, nothing to provision.
// Server half — sign the signed-in user. No secret setup needed.
const bf = createBrandfineClient({ apiKey })
const [config, identityToken] = await Promise.all([
bf.liveChat.getConfig(),
bf.liveChat.identityToken(user.id),
])
const visitor = {
externalId: user.id,
name: user.name,
email: user.email,
attributes: { plan: user.plan },
identityToken,
}
// → pass BOTH config and visitor to your client component// Client half — serialization only, no crypto
void bf.liveChat.install({ config, visitor })An invalid or missing identityToken never breaks the chat — the
conversation silently starts anonymous (and the failure is logged on
the Brandfine side), so a misconfigured integration degrades to
today's behavior instead of blocking visitors. Omit visitor
entirely on public pages: anonymous chat is unchanged.
identityToken() is server-only — it throws when called in a
browser context (deriving there would put the API key next to the
DOM). An explicit secret ({ secret } option or
BRANDFINE_LIVE_CHAT_IDENTITY_SECRET env) overrides the derived
default; use it only when the signing service shouldn't hold the
broad API key, or on legacy workspaces without a revealable key —
generate one in the CMS: Plugins → Live Chat → Integrate.
Localization
Configure per-locale greetings and away messages in the CMS
(Plugins → Live Chat → Manage settings → Translations). The widget
asks for the rendered page's language automatically — <html lang> first, browser language second — so a bilingual site shows
each visitor the right text with no extra code. To pin it explicitly
(e.g. your i18n router's active locale):
bf.liveChat.install({ config, locale })Resolution is server-side: exact tag → primary subtag (tr-TR →
tr) → the default text. The page language is also stored on the
conversation, so operators see which language the visitor expects
before reading a word.
Building your own chat UI
install() renders Brandfine's floating bubble. If chat should live
inline in your own design — a panel in the page flow — use
createSession(): the same
conversation, identity and threading, with the transcript exposed as
typed, subscribable state and the pixels entirely yours.
One thing a custom UI owns that the widget does for you: links.
Message bodies are plain text, and the pages the assistant recommends
arrive as data alongside them — so anchors are yours to render. It is
a few lines with segmentChatMessage(); see
Links in messages.
Plain HTML alternative
No SDK? Paste the snippet from Plugins → Live Chat → Manage settings → Integrate in the CMS:
<div data-bf-live-chat data-publishable-key="brandfine_pk_…"></div>
<script src="https://api.brandfine.co/widgets/live-chat.js" defer></script>Same widget, same behavior. The publishable key can only start chat conversations for its workspace — it is designed to be public.
Theming
The widget renders in a Shadow DOM; your site's CSS can't break it
and vice versa. Brand it via --bf-chat-* custom properties — set
them in the CMS (they arrive through theme) or inline on the host
div:
<div
data-bf-live-chat
data-publishable-key="brandfine_pk_…"
style="--bf-chat-primary:#0b1f2a; --bf-chat-radius:10px;"
></div>Available: --bf-chat-primary, --bf-chat-primary-contrast,
--bf-chat-surface, --bf-chat-text, --bf-chat-muted,
--bf-chat-border, --bf-chat-visitor-bubble, --bf-chat-radius,
--bf-chat-font, --bf-chat-offset, --bf-chat-z.