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