Brandfine Docs
SDK

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 won

Unlike 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.

On this page