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