Docs menu

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.

HTML
<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

AttributeDefaultDescription
data-keyrequiredYour public key.
data-apihttps://api.kavchat.comAPI base URL.
data-localethe browser languageLanguage 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-tokennoneIdentity token for a signed-in user. See Identify users.
data-user-namenoneShown in the greeting ({name}) and prefilled in the contact form.
data-openfalse"true" opens the chat as soon as it loads.
HTML
<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:

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

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

CSS
/* while your cookie banner is shown */
body.cookie-banner-open { --kavchat-bottom: 80px; }
JavaScript
// 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:

typeFieldsWhen
readyversion, offlineThe bot loaded, from the network or from the saved copy when offline.
unavailablereason? ('quota')The bot is off, limited to testers, or over quota. Nothing is shown.
open, closeThe panel opened or closed.
effecteffectA button asked for the contact form, a link or a screen.
ticketid, numberA contact request was created.
livestate, idThe chat with a representative changed state: queued, active, closed or ticket.
JavaScript
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: and tel: 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.

JavaScript
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}`;
  },
});
  • id is the value onScreen receives: letters, digits, _, . and -, up to 40 characters. label is 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), and onScreen receives it as targetId.
  • 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:

CSP
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 localStorage per 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 localStorage per public key, with its ETag. Each load sends If-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 localStorage and 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:

Terminal
yarn add @kavchat/widget
# or: npm install @kavchat/widget
JavaScript
import { createWidget } from '@kavchat/widget';

const widget = createWidget({ publicKey: 'pk_live_...' });
await widget.ready;
widget.open();