Skip to main content

Chat

ChatClient from @cstar.help/js/chat powers customer chat. Real-time delivery is on by default and falls back to polling without a code change. Identify with HMAC for known users, or stay anonymous for self-serve flows.

Setup

setup.js
import { ChatClient } from '@cstar.help/js/chat';

const chat = new ChatClient({
  teamSlug: 'acme',
  // realtime: true,                  // Default. Set false to force polling.
  // pollingInterval: 5000,           // ms — fallback poll cadence (default 5000)
});

Identify (HMAC)

For logged-in customers, your server signs an HMAC of the customer payload using your team's chat-secret. The widget never holds the secret; it just passes the signature along.

identify.js
// 1) Server-side: sign the payload
import { createHmac } from 'node:crypto';

function signCustomer(customer, secret) {
  return createHmac('sha256', secret).update(JSON.stringify(customer)).digest('hex');
}

// 2) Client-side: pass the signature
const customer = { id: 'cus_external_42', email: 'jane@acme.com', name: 'Jane' };
await chat.identify(customer, signature);

Anonymous mode

Skip identify() for one-shot help requests — the chat client will create a short-lived anonymous session. Anonymous callers can create new tickets and read their own messages, but per-user list calls (e.g. chat.tickets.list()) will throw a typed ChatClientAuthError with a recovery hint.

Tickets sub-API

tickets.js
// Create a ticket as the identified customer
const ticket = await chat.tickets.create({ title: 'Help with my account' });

// List the customer's own tickets (auth required)
const { data } = await chat.tickets.list({ status: 'open', limit: 20 });

// Get one
const detail = await chat.tickets.get(ticket.id);

Messages + realtime

messages.js
// Read existing messages
const { data: messages } = await chat.messages.list(ticket.id);

// Send
await chat.messages.send(ticket.id, 'Thanks for helping!');

// Subscribe to new messages — uses SSE if available, polling fallback otherwise
const offMsg = chat.onMessage(ticket.id, (msg) => {
  console.log(msg.sender_name, msg.content);
});

// Typing indicators
const offTyping = chat.onTyping(ticket.id, (event) => {
  console.log(event.sender, event.isTyping);
});
await chat.sendTyping(ticket.id, true);

// Tear down on unmount
offMsg();
offTyping();
chat.disconnect();

Reactive wrappers

The framework SDKs wrap this client so you don't manage subscribe/teardown by hand: useChat + useTickets for React, ChatState + TicketsState for Svelte.

Next up