Website widget
The widget is the chat your visitors see on your website. It loads the bot you published and runs the whole conversation in the browser. No language model, no third-party requests, about 22 KB gzip. Live chat with a representative is a separate file, loaded only when you turn it on.
Install
Paste this before </body> on every page where the bot should appear. The dashboard shows it with your key on the Install page.
<script src="https://cdn.kavchat.com/v1/kavchat.js" data-key="pk_live_..." async></script>The public key (pk_live_...) is safe to put in a page. Never put the secret key (sk_live_...) in a page.
Attributes
| Attribute | Default | Description |
|---|---|---|
data-key | required | Your public key. |
data-api | https://api.kavchat.com | API base URL. |
data-locale | the browser language | Language of the widget chrome (buttons, form, notices): he, en, ar, ru, es, fr, de, pt, it, tr. Without a translation, the bot language is used. Your script content is always shown as you wrote it. he and ar render right to left. |
data-user-token | none | Identity token for a signed-in user. See Identify users. |
data-user-name | none | Shown in the greeting ({name}) and prefilled in the contact form. |
data-open | false | "true" opens the chat as soon as it loads. |
<script
src="https://cdn.kavchat.com/v1/kavchat.js"
data-key="pk_live_..."
data-locale="he"
data-user-token="<identity JWT>"
data-user-name="Dana"
data-open="false"
async></script>JavaScript API
The script installs window.Kavchat:
Kavchat.init({
publicKey: 'pk_live_...',
apiBase: 'https://api.kavchat.com', // optional
locale: 'he', // optional
userToken: '<identity JWT>', // optional
userName: 'Dana', // optional
open: false, // optional
onScreen(screen, targetId) {}, // optional: "screen" buttons in your scripts
onEvent(event) {}, // optional: ready, unavailable, open, close, effect, ticket
});
Kavchat.open();
Kavchat.close();
Kavchat.toggle();
Kavchat.setUser({ token: '<identity JWT>', name: 'Dana' }); // on login; setUser(null) on logout
Kavchat.offset({ bottom: 80 }); // lift the launcher, e.g. above a cookie banner; offset(null) resets
Kavchat.destroy();init is not needed when the script tag has data-key. Calling init again replaces the running widget.
Calling the API before the script loads
The script tag is async, so your code may run first. Create a queue and push calls into it. The widget runs them in order when it loads. init always runs first, and its options are merged over the script tag attributes.
<script>
window.Kavchat = window.Kavchat || { q: [] };
Kavchat.q.push(['init', { publicKey: 'pk_live_...', onScreen: function (s, id) { location.href = '/' + s + '/' + id; } }]);
Kavchat.q.push(['setUser', { token: '<identity JWT>', name: 'Dana' }]);
</script>
<script src="https://cdn.kavchat.com/v1/kavchat.js" async></script>Each entry is [methodName, ...args]. After the script has loaded, Kavchat.q.push(...) still works and runs the call immediately, so the same code works in both cases.
Space for a cookie banner
If your site has a fixed element at the bottom of the screen, such as a cookie banner, you can move the button, window and bubbles above it. There are two ways to do this: the --kavchat-bottom CSS variable or the Kavchat.offset command, which accepts a number in pixels or a length value such as 5rem. The offset is added to the height set in the admin panel. When the banner closes, remove the variable or call offset(null). Both methods also work on sites with a strict CSP. On phones, the window opens full screen and does not move.
/* while your cookie banner is shown */
body.cookie-banner-open { --kavchat-bottom: 80px; }// works before or after the widget script loads
window.Kavchat = window.Kavchat || { q: [] };
Kavchat.q.push(['offset', { bottom: banner.offsetHeight + 12 }]);
// after the visitor makes a choice
Kavchat.q.push(['offset', null]);Events
onEvent receives one of:
type | Fields | When |
|---|---|---|
ready | version, offline | The bot loaded, from the network or from the saved copy when offline. |
unavailable | reason? ('quota') | The bot is off, limited to testers, or over quota. Nothing is shown. |
open, close | The panel opened or closed. | |
effect | effect | A button asked for the contact form, a link or a screen. |
ticket | id, number | A contact request was created. |
live | state, id | The chat with a representative changed state: queued, active, closed or ticket. |
Kavchat.init({
publicKey: 'pk_live_...',
onEvent(event) {
if (event.type === 'ready') console.log('Bot version', event.version, event.offline ? '(offline copy)' : '');
if (event.type === 'ticket') console.log('Request opened', event.number);
},
});Script actions
- Contact opens the contact form inside the chat. The conversation transcript is attached to the request.
- Link opens
https:andtel:links in a new tab (rel="noopener noreferrer"). Other schemes are ignored. - Screen calls your
onScreen(screen, targetId). Without it the button does nothing on the web.
The screen list in the editor
Declare the screens your app can open, once in the screens option of Kavchat.init. The SDK sends the list to Kavchat by itself, and the script editor then offers these screens in a dropdown with their names, instead of a free text field. It also asks for an item id when a screen needs one, and warns about a button that points to a screen you did not declare. Nobody types the screen list by hand in the dashboard.
Kavchat.init({
publicKey: 'pk_live_...',
screens: [
{ id: 'orders', label: 'My orders' },
{ id: 'product', label: 'Product page', needsTarget: true }, // the button also carries a product id
],
onScreen(screen, targetId) {
location.href = targetId ? `/${screen}/${targetId}` : `/${screen}`;
},
});idis the valueonScreenreceives: letters, digits,_,.and-, up to 40 characters.labelis the name the admin sees, up to 60 characters. Up to 100 screens. Invalid entries and duplicate ids are dropped.needsTarget: the button also carries an item id (for example a product id), andonScreenreceives it astargetId.- The list is sent in the background after the release loads, only when it changed or once a week. A failure is silent and retried at the next launch. The user never sees anything.
- The registered screens appear in the dashboard under Bot, Settings, "App screens", with the platforms that reported each one. A screen you removed from the app can be deleted there.
Identifying your users
To tag sessions and requests with your own user id, or to show the bot only to testers, sign a JWT on your backend and pass it as data-user-token or userToken. Call Kavchat.setUser(...) when the user signs in or out. The widget sends the token as Authorization: Bearer <token> on every call, and signed-in users may open a request without leaving an email or a phone. How to create the token: Identify users.
Content Security Policy
The widget works on sites with a strict CSP, without 'unsafe-inline' or 'unsafe-eval'. You only need to allow our two origins:
script-src https://cdn.kavchat.com;
connect-src https://api.kavchat.com;Styles are loaded as constructable stylesheets inside the Shadow DOM, so there is no need to change style-src. If the bot has its own image or the scripts contain images, add the server those images are loaded from to img-src. The widget does not load anything from any other origin or open an iframe.
Live chat with a representative
There is nothing to set up in your site. When live chat is turned on in the dashboard, the widget loads one more file, kavchat-live.js, from the same folder and the same version as kavchat.js. Sites without live chat never download it.
- The option: next to "Talk to our team" the bot shows "Talk to a representative", only after the server confirms that a representative is free. Outside business hours, or when everyone is busy, only the contact form is offered, with a short line saying why, and when the team is back if that is known. When the monthly quota is used up, the form is shown without an explanation.
- The request: guests leave an email or a phone, in case nobody is free in time, and can add a name and a first message. When more than one department is open, they choose one. Signed-in users (
userToken) do not fill in contact details. If nobody can take the chat, the contact form opens with what the visitor already typed. - Waiting: "Waiting for a representative" with a countdown to the end of the wait time you set, and the buttons "Leave a message instead" and "End chat". Visitors can write while they wait. When the time is up the chat becomes a request automatically, and the visitor sees that nobody was free in time and the request number.
- The chat: the representative's name, messages, a typing indicator, transfers to another representative, and ending the chat. Everything from the server is shown as plain text, with no HTML and no links. Screen readers announce new messages, and when the panel is closed a dot appears on the launcher.
- Real time: a Server-Sent Events connection. If it drops, the widget reconnects with growing delays and catches up on everything it missed, so no message is lost or shown twice. On networks that block this connection it switches to repeated requests (long-poll). Every message has an id, so a retry never creates a duplicate. A message that was not sent is marked, and a tap sends it again.
- Reload: the chat access credential is kept in
localStorageper public key and conversation, never in a URL. It is valid for up to 12 hours or until the chat ends, so after a reload or on another page the whole chat comes back: the part with the bot, then the part with the representative. - No preflight: the calls send no custom headers, and request bodies are JSON sent as
text/plain.
AI answers (Premium)
There is nothing to set up in your site. When AI answers are turned on in the dashboard (on your own AI provider key), a typed question that no script matches is sent to Kavchat, and the chat shows a typing indicator while it waits, for up to 20 seconds. The answer is shown as plain text with a small "AI answer" label in the user's language, followed by a button for a related topic when there is one, and "Talk to our team".
If there is no answer, or the request fails, takes too long or hits the limit (10 answers per conversation every 15 minutes), the chat shows its regular "no match" reply. The question still appears in the unanswered questions report, and AI answers are kept in the saved conversation like any other message.
How it behaves
- Isolation: everything renders inside a Shadow DOM. Your CSS does not reach the widget, and the widget's CSS does not reach your page.
- Caching: the published bot is saved in
localStorageper public key, with its ETag. Each load sendsIf-None-Match, and when nothing changed the server answers 304. When the network is down the saved copy is used and a notice is shown. - Sessions: a random session id is kept in
sessionStorage, so moving between pages keeps the same conversation. - Live chat: an open chat with a representative is kept in
localStorageand restored on the next page. - Analytics: the conversation path is reported about 2 seconds after each action, and when the page is hidden.
- Kavchat badge: appears on all plans. On Premium, you can hide it in the bot settings.
- Accessibility: the panel is a labelled dialog, focus moves into it and back, Esc closes it, buttons are real buttons, new bot messages are announced, and motion is reduced when the visitor asks for it.
Install from npm
For a single-page app you can bundle the widget instead of loading the script tag:
yarn add @kavchat/widget
# or: npm install @kavchat/widgetimport { createWidget } from '@kavchat/widget';
const widget = createWidget({ publicKey: 'pk_live_...' });
await widget.ready;
widget.open();