API Docs

@livechat/sdk-js

AdminUpdated Sep 19, 2026

@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, so Chatly.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 a typeof window guard — see SSR below.

Install

pnpm

pnpm add @livechat/sdk-js

npm

npm install @livechat/sdk-js

Ships 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

publicId (required)

string

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.

apiUrl

string

Your Chatly API host. Set this — the default is an internal development host and is wrong for every real deployment. Default: https://api.livechat.local.

Methods

Instance methods

Name

Type

Description

identify(input)

async

Attach a known identity. Returns { contactId }. Re-initialises the session against POST /v1/widget/init — there is no separate identify route — and stores the rotated token it gets back. See Identity Verification for userHash.

track(name, properties?)

async

Fire a CDP event. See Audiences + CDP.

send(conversationId, body, attachments?)

async

Send a message as the current visitor into an existing conversation. attachments are records returned by uploadFile.

uploadFile(file)

async

Presign, then PUT the file straight to object storage. Returns a VisitorAttachment to pass to send. The bytes never transit the API.

rate(conversationId, score, comment?)

async

Submit a post-chat satisfaction score (1–5, or 0–10 for NPS).

emailTranscript(conversationId, email)

async

Email this visitor a transcript of their conversation.

open() / close()

sync

Delegate to window.Chatly, so they only do anything on a page that also loaded the widget bundle. No-ops otherwise — they will not mount a widget by themselves.

Info — You need a conversation ID first

send, rate and emailTranscript all take a conversationId, and this SDK exposes no way to list or start one — the widget bundle owns that. Get the ID from the widget's conversation event, or call POST /v1/widget/conversations directly.

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, emailTranscript or uploadFile fetches one if the browser has none. identify does not mint an anonymous session first — the token it stores is the identified one.

  • A dead token repairs itself. A 403 whose error code begins visitor_session_ means the stored token expired or predates signed sessions. The SDK drops it, fetches a fresh one, and replays the call once. Other 403s — 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

externalId

string

Your own ID for this person. Optional — but it is the only field identity verification can bind, so a verified session needs it.

email

string

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.

phone

string

Optional.

name

string

Optional display name.

userHash

string

HMAC-SHA256(idvSecret, externalId), computed by your server. Never in the browser — the secret would be in your page source. See chatlyIdentityHash.

attributes

object

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 export

  • SdkOptions, 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 gate track() behind your own consent state.

Source

github.com/livechat/platform — packages/sdk-js.

Was this page helpful?