@livechat/sdk-js
- Written for
- + Written for
- Deprecated
- + Deprecated
- Applies to
- + Applies to
@livechat/sdk-js
The official browser-side SDK — identify visitors, fire CDP events, upload files, and open the widget from your own UI.
@livechat/sdk-js is the browser-side companion to the server SDK. It gives your application code a typed way to do what the visitor could do by hand: attach an identity, fire CDP events, send a message, upload a file, rate a conversation, and open or close the widget.
Every call it makes goes to a /v1/widget/* endpoint and authenticates as the visitor — with a session token the SDK fetches and stores for you. It carries no API key and can do nothing an agent can do. For the agent-side API, use the server SDK from a backend.
Info — Use the loader, not the SDK, for plain HTML
If you just want the chat widget on a marketing site, paste the loader snippet. The SDK is for richer integrations — React/Vue/Svelte apps that want typed control over identity and events.
Warning — Browser only
The constructor reads the stored visitor token out of
localStorage, soChatly.init()throws in Node, in a Cloudflare Worker, in a Vercel Edge function, and during SSR. There is no memory-storage adapter and no DOM-free build. Construct it inside an effect or behind atypeof windowguard — see SSR below.
Install
pnpm
pnpm add @livechat/sdk-jsnpm
npm install @livechat/sdk-jsShips ESM with native TypeScript types and no runtime dependencies. About 3 KB gzipped. There is no CDN build — the CDN hosts loader.js and widget.js, which are the widget, not this package.
Quick start
import { Chatly } from '@livechat/sdk-js';
const lc = Chatly.init({
publicId: 'CH_abc123',
apiUrl: 'https://api.chatlychat.com',
});
await lc.identify({
externalId: 'user_42',
email: '[email protected]',
name: 'Jamie',
userHash: window.__CHATLY_USER_HASH__, // from your server
});
await lc.track('checkout.completed', {
orderId: 'O-7421',
amount: 199,
});
lc.open();Warning — Chatly.init(), not new Chatly()
The constructor is private.
Chatly.init(options)is the only way to build an instance.
Configuration
Chatly.init() takes exactly two options. There is no theme, locale, storage or callback configuration here — the widget's appearance and behaviour are configured on the loader's init options and in the dashboard, not on this SDK.
SdkOptions
Name | Type | Description |
|---|---|---|
|
| The channel's public ID. Found at Channels → Web → public ID. Also scopes the stored visitor token, so two channels on one page keep separate sessions. |
|
| Your Chatly API host. Set this — the default is an internal development host and is wrong for every real deployment. Default: |
Methods
Instance methods
Name | Type | Description |
|---|---|---|
|
| Attach a known identity. Returns |
|
| Fire a CDP event. See Audiences + CDP. |
|
| Send a message as the current visitor into an existing conversation. |
|
| Presign, then PUT the file straight to object storage. Returns a |
|
| Submit a post-chat satisfaction score (1–5, or 0–10 for NPS). |
|
| Email this visitor a transcript of their conversation. |
|
| Delegate to |
Info — You need a conversation ID first
send,rateandemailTranscriptall take aconversationId, and this SDK exposes no way to list or start one — the widget bundle owns that. Get the ID from the widget'sconversationevent, or callPOST /v1/widget/conversationsdirectly.
Sessions
The SDK holds a visitor session token in localStorage, keyed per publicId. Two behaviours follow, and neither needs anything from you:
A token is minted on demand. The first
track,send,rate,emailTranscriptoruploadFilefetches one if the browser has none.identifydoes not mint an anonymous session first — the token it stores is the identified one.A dead token repairs itself. A
403whose errorcodebeginsvisitor_session_means the stored token expired or predates signed sessions. The SDK drops it, fetches a fresh one, and replays the call once. Other403s — a disallowed origin, a missing identity hash — are not retried, because refetching a token does not cure them.
Every request carries a freshly minted Idempotency-Key, so a stray network-level retry cannot double-write.
React example
'use client';
import { useEffect, useRef } from 'react';
import { Chatly } from '@livechat/sdk-js';
export function ChatlyProvider({ user, userHash, children }: {
user: { id: string; email: string; name: string } | null;
userHash: string | null;
children: React.ReactNode;
}) {
const lcRef = useRef<Chatly | null>(null);
useEffect(() => {
lcRef.current = Chatly.init({
publicId: process.env.NEXT_PUBLIC_CHATLY_CHANNEL_ID!,
apiUrl: process.env.NEXT_PUBLIC_CHATLY_API_URL!,
});
}, []);
useEffect(() => {
if (!lcRef.current || !user || !userHash) return;
void lcRef.current.identify({
externalId: user.id,
email: user.email,
name: user.name,
userHash,
});
}, [user, userHash]);
return <>{children}</>;
}There is no destroy() on this SDK — the instance holds no listeners and no DOM. To tear the widget down, call window.Chatly.destroy().
Identify
IdentifyInput
Name | Type | Description |
|---|---|---|
|
| Your own ID for this person. Optional — but it is the only field identity verification can bind, so a verified session needs it. |
|
| An email-only identity is accepted. It attaches a reply address to whoever this browser already is; it can never be verified, because there is no external ID to bind. |
|
| Optional. |
|
| Optional display name. |
|
|
|
|
| Free-form custom attributes. |
CDP event helpers
// Common e-commerce events
lc.track('product.viewed', { sku: 'WIDGET-1', price: 49.99 });
lc.track('cart.added', { sku: 'WIDGET-1', quantity: 2 });
lc.track('checkout.started', { total: 99.98, currency: 'USD' });
lc.track('checkout.completed', { orderId: 'O-7', total: 99.98, currency: 'USD' });
// SaaS events
lc.track('signup.completed', { plan: 'starter' });
lc.track('onboarding.step_completed', { step: 3, of: 5 });
lc.track('subscription.upgraded', { from: 'starter', to: 'business' });Event names are free-form strings. There is no page() helper — send page views as a track call with the URL in the properties.
File attachments
const attachment = await lc.uploadFile(fileInput.files[0]);
await lc.send(conversationId, 'Here is the receipt', [attachment]);VisitorAttachment is { id, filename, mimeType, sizeBytes, url }.
TypeScript
Every public method is typed. The SDK exports:
Chatly— named and as the default exportSdkOptions,IdentifyInput,TrackInput,VisitorAttachment
import type { IdentifyInput, VisitorAttachment } from '@livechat/sdk-js';No @types/... install needed. Note that failures throw a plain Error (Chatly SDK request failed: <status>) — there is no typed error class in this package.
Server-side rendering (Next, Remix, SvelteKit)
Don't call Chatly.init() during SSR — it reads localStorage. Two patterns:
Dynamic import
'use client';
import { useEffect } from 'react';
export function ChatlyClient() {
useEffect(() => {
void import('@livechat/sdk-js').then(({ Chatly }) => {
Chatly.init({ publicId: 'CH_...' });
});
}, []);
return null;
}SSR guard
import { Chatly } from '@livechat/sdk-js';
const lc = typeof window === 'undefined'
? null
: Chatly.init({ publicId: 'CH_...' });Privacy
The SDK only ever talks to the apiUrl you configure. It does not phone home, does not load third-party trackers, and does not write cookies — the visitor token lives in localStorage under livechat:vt:<publicId>.
Warning — Consent is your call, not ours
The SDK does not read Do-Not-Track or Global Privacy Control and has no
setConsent(). Nothing is sent unless your code calls a method, so gatetrack()behind your own consent state.