SDK
Control the widget from your page with window.KauZap, or draw your own chat with
the headless client.
Widget
The loader snippet creates window.KauZap. Calls made before the widget is ready are queued and run in order.
If you bundle your front end, you can mount the widget from the package instead:
import { init } from '@kauzap/sdk', then
const kauzap = init({ propertyKey: 'pk_…', widgetUrl: 'https://widget.kauzap.com/' }).
It returns the same methods.
| Method | What it does |
|---|---|
identify(token) | Signs the visitor in with an identity token from your server |
setAttributes(attributes) | Adds page context, such as the current plan or cart value. Agents see these labelled "Set by the page, not verified", so never rely on them for anything that matters. Use the identity token or a signed URL for trusted data. |
open(), close() | Opens or closes the chat window |
sendMessage(text) | Sends a message as the visitor, for example from your own "Ask about this order" button |
on(event, listener) | Subscribes to an event and returns a function that unsubscribes |
logout() | Ends the visitor session on this device. Call it when the user signs out of your app. |
Events
| Event | Listener gets |
|---|---|
ready | Nothing. The widget has loaded. |
open, close | Nothing |
unread | The unread message count |
message | Ids and sender of a new message, without its text, so you can show your own notification |
const off = window.KauZap.on('unread', (count) => {
document.title = count ? `(${count}) My shop` : 'My shop';
});
Headless client
For apps that draw their own chat interface. It uses the same session, identity and realtime connection as the widget, with no UI. After a reconnect it replays anything missed, so your view stays complete.
import { createClient } from '@kauzap/sdk';
const client = createClient({
propertyKey: 'pk_your_property_key',
apiUrl: 'https://YOUR_PROJECT.supabase.co/functions/v1/api',
supabaseUrl: 'https://YOUR_PROJECT.supabase.co',
supabaseKey: 'your publishable key',
});
await client.identify(await getTokenFromYourServer());
client.on('message', (message) => render(message));
await client.sendMessage('Hi, where is my order?');
| Member | What it does |
|---|---|
start({ challengeToken? }) | Starts an anonymous session for this device, unless one is active. If it fails with challenge_required (the error's details carry provider, site_key and retry_after_seconds), show your challenge and call start again with the solved token. Each token works once. |
identify(token) | Signs the visitor in; resolves with the session |
setAttributes(attributes) | Same as the widget: page-set and not verified. A null value removes a key. |
conversations() | The visitor's conversations, newest first |
openConversation(id), startConversation() | Loads or starts a conversation and subscribes to it |
sendMessage(text) | Sends in the open conversation, or starts one |
session, conversation | The current session and the open conversation |
on(event, listener) | Typed events: session, message, change, error. Returns an unsubscribe function. |
logout() | Closes the connection and forgets this device's visitor |
The headless client runs in your page, so it keeps the anonymous device id in your page's
localStorage (key kz.device), where your own scripts can read it.
Pass a storage option to keep it elsewhere. logout() removes it.
Challenge on bursts
When a property has Challenge bursts of new chats on and many anonymous
chats start from one network, start() rejects with code
challenge_required. The widget handles this itself. With the headless client,
show the challenge named in the error's details (provider is
turnstile, Cloudflare Turnstile) and start again with the token. Your page's
hostname must be on the KauZap Turnstile widget's hostname list (ask the KauZap team), and
your Content-Security-Policy must allow https://challenges.cloudflare.com in
script-src and frame-src. If provider is missing,
wait retry_after_seconds and try again.
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit"></script>
try {
await client.start();
} catch (error) {
if (error.code !== 'challenge_required' || error.details.provider !== 'turnstile') throw error;
turnstile.render('#challenge', {
sitekey: error.details.site_key,
// Each token works once; a rejected one gives another challenge_required.
callback: (token) => client.start({ challengeToken: token }),
});
}