Embed script & control

Updated July 2026

One script tag puts your widget on any page. After that, the window.Qalyb API is your remote control — open, close, toggle, send messages, and start or end voice, all wired to your own buttons and app logic.

How the loader works

Every Qalyb widget you create in Deploy is backed by a secret widget token. The token connects one agent to your site. The public loader at https://cdn.qalyb.ai/widget.js reads that token, fetches the widget's saved appearance and behavior, mounts the UI, and exposes a control API on window.Qalyb.

The bundle is the @qalyb/widget package — a tiny Preact app. It does two things on load: it auto-mounts if it finds a script tag carrying data-qalyb-token, and it attaches the window.Qalyb object so you can drive the widget yourself.

Paste your full token:
the embed snippets in the dashboard show only your token's short prefix followed by ... as a placeholder. The full secret is shown exactly once, at widget creation. Substitute that real value for data-qalyb-token / token before going live — the prefix alone will not authenticate.

1. Drop in the script tag (recommended)

The simplest install: paste one <script> tag before </body>. On DOMContentLoaded the loader finds the tag by its data-qalyb-token attribute, reads every other data-qalyb-* attribute as config, and mounts the widget — no further code needed.

index.html · html
<script
  src="https://cdn.qalyb.ai/widget.js"
  data-qalyb-token="wgt_yourfulltoken"
  data-qalyb-mode="chat"
  data-qalyb-primary-color="#5c5cf6"
  data-qalyb-title="Acme Support"
  data-qalyb-subtitle="Online now"
  data-qalyb-launcher-label="Ask Acme"
  data-qalyb-launcher-variant="pill"
></script>

Copy a pre-filled version of this snippet (with your appearance already baked in) from the Embed tab of the widget editor.

app.qalyb.ai/channels/widgets/[id]
Screenshot: the widget editor's Embed tab showing the Script Tag, JavaScript API, and React Component snippets with copy buttons
Deploy → your widget → Embed: copy the Script Tag, JavaScript API, or React Component snippet.

Common data-qalyb-* attributes

AttributeValuesNotes
data-qalyb-tokenstringRequired. Your full widget token. The presence of this attribute auto-mounts.
data-qalyb-modechat / voiceMatches the widget's type. A Chat Widget is chat; a Voice Widget is voice.
data-qalyb-surfaceclassic / voice-widgetclassic is the chat bubble; voice-widget is the voice orb canvas.
data-qalyb-themelight / dark / autoDefaults to light.
data-qalyb-positionbottom-right / bottom-leftLauncher and panel anchor.
data-qalyb-primary-colorhexBrand color, e.g. #5c5cf6. Also data-qalyb-accent-color.
data-qalyb-titlestringHeader title. Also data-qalyb-subtitle and data-qalyb-welcome-message.
data-qalyb-launcher-labelstringPill text, e.g. Ask Acme. Pair with data-qalyb-launcher-variant="pill".
data-qalyb-launcher-iconchat / bot / sparkles / voice / help / headset / zap / smileLauncher icon glyph.
data-qalyb-start-call-labelstringText for the voice widget's start-call button, e.g. Talk to me.
data-qalyb-start-call-displayicon / text / bothWhether the voice widget's start-call button shows the phone icon, the label text, or both. Pairs with data-qalyb-start-call-label.
data-qalyb-launcher-varianticon / text / pillLauncher button content: icon only, label text only, or icon + text (pill). Pairs with data-qalyb-launcher-label.
data-qalyb-display-modetransparent / fullscreen / floatingHow the voice widget's orb opens: floating transparently over the page (default), as a branded full-screen takeover, or inside a compact floating panel.
data-qalyb-launcher-positiontop-leftbottom-right9-point anchor for the voice widget launcher (and floating panel): top/middle/bottom × left/center/right, e.g. bottom-center.
data-qalyb-auto-opentrueOpen the panel automatically on page load instead of showing only the launcher.
Note:
attributes mirror the saved customization, so most of the time you can leave them off and manage look-and-feel in the dashboard. Anything you do pass on the tag overrides the saved value for that page.

2. Or initialize from JavaScript

If you prefer to control mounting yourself — for example, only after a consent banner is accepted — load widget.js without the data-qalyb-token attribute and call window.Qalyb.init() when you're ready. The config object takes the same fields as the data attributes, in camelCase.

init.html · html
<script src="https://cdn.qalyb.ai/widget.js"></script>
<script>
  window.Qalyb.init({
    token: "wgt_yourfulltoken", // your full widget token
    mode: "chat",
    theme: "light",
    position: "bottom-right",
    primaryColor: "#5c5cf6",
    accentColor: "#06B6D4",
    borderRadius: 16,
    title: "Acme Support",
    subtitle: "Online now",
    welcomeMessage: "Hi! How can we help?",
    autoOpen: false,
    surface: "classic",
    launcherLabel: "Ask Acme",
    launcherIcon: "chat",
    launcherVariant: "pill",
  });
</script>
Note:
calling init() again replaces the current instance — the loader destroys the existing widget first, so it is safe to re-init with new config.

3. Or use the React component

For React and Next.js apps, import QalybWidget from the @qalyb/widget package. It mounts on render, unmounts on cleanup, and accepts the same config as props plus lifecycle callbacks (covered below).

App.tsx · tsx
import { QalybWidget } from '@qalyb/widget';

export function App() {
  return (
    <QalybWidget
      token="wgt_yourfulltoken" // your full widget token
      mode="chat"
      theme="light"
      position="bottom-right"
      primaryColor="#5c5cf6"
      accentColor="#06B6D4"
      borderRadius={16}
      title="Acme Support"
      subtitle="Online now"
      launcherLabel="Ask Acme"
      launcherIcon="chat"
      launcherVariant="pill"
      autoOpen={false}
      surface="classic"
      onOpen={() => console.log('widget opened')}
      onMessage={(m) => console.log('message', m)}
      onCallEnd={(s) => console.log('call ended', s)}
    />
  );
}
Package availability:
@qalyb/widget ships the CDN bundle and the React component. The script-tag install at cdn.qalyb.ai/widget.js works on any site today and needs no package install. Use the React import where you already have a build pipeline.

The control API

Once widget.js has loaded, window.Qalyb exposes these methods. They all act on the single mounted widget instance.

MethodReturnsWhat it does
Qalyb.init(config)voidMount (or remount) the widget with the given config object.
Qalyb.open()voidOpen the chat or voice panel.
Qalyb.close()voidCollapse the panel back to the launcher.
Qalyb.toggle()voidOpen if closed, close if open.
Qalyb.sendMessage(content)Promise<ChatMessage>Send a chat message as the visitor; resolves with the agent's reply.
Qalyb.startVoice()Promise<void>Begin a voice session (voice-enabled widgets only).
Qalyb.endVoice()voidEnd the active voice session.
Qalyb.destroy()voidUnmount the widget and remove it from the page.
These are the only methods:
init, open, close, toggle, sendMessage, startVoice, endVoice, and destroy are the complete public surface of window.Qalyb. There is no documented method beyond these — anything else is not part of the contract.

Open, close, toggle

The most common pattern is wiring the widget to a button you already have on the page — a "Need help?" link in your nav, a hero CTA, or a footer item. Hide the built-in launcher (set hideLauncher on the embed) and drive everything yourself.

controls.ts · ts
// The launcher button on your page
const button = document.querySelector('#open-support');

button.addEventListener('click', () => {
  window.Qalyb.open();
});

// Toggle from anywhere
document.querySelector('#help-link')?.addEventListener('click', () => {
  window.Qalyb.toggle();
});

// Close it when the user finishes a flow
window.Qalyb.close();

Send a message programmatically

sendMessage() posts a message as if the visitor typed it and resolves with the agent's ChatMessage reply (id, role, content, timestamp). Use it to seed a conversation from a button — for example, a "Track my order" CTA that opens the widget and asks the question for the user.

send-message.ts · ts
// Send a message on the visitor's behalf (chat-enabled widgets).
// Resolves with the agent's reply.
try {
  const reply = await window.Qalyb.sendMessage('What are your opening hours?');
  console.log(reply.role, reply.content); // "assistant", "We're open 9–6 ..."
} catch (err) {
  console.error('Send failed:', err.message);
}

Start and end voice

For Voice Widgets, startVoice() begins a session and endVoice() hangs up. This lets a single "Talk to us" button on your page launch the agent's voice orb.

voice.ts · ts
// Start a voice session (voice-enabled widgets only).
const callBtn = document.querySelector('#talk-to-us');
callBtn.addEventListener('click', () => {
  window.Qalyb.startVoice();
});

// End the current voice session
window.Qalyb.endVoice();
Voice requires a voice-enabled widget:
startVoice() only works when the widget's mode allows voice. A chat-only token rejects voice on the server with 403 Voice mode not enabled for this widget, and a voice-only token rejects chat messages the same way. Match the method you call to the widget's type.

Tear down and remount

In single-page apps, call destroy() on route changes where the widget shouldn't appear, and init() again where it should. (The React component handles this for you automatically on mount and unmount.)

teardown.ts · ts
// Tear the widget down (e.g. on a single-page-app route change)
window.Qalyb.destroy();

// Re-create it later with fresh config
window.Qalyb.init({ token: 'wgt_yourfulltoken', mode: 'chat' });

Events & lifecycle callbacks

The React QalybWidget component accepts callback props that fire as the visitor interacts. These are the events Qalyb surfaces — use them for analytics, support handoffs, or custom UI:

CallbackFires whenPayload
onOpenThe panel opens.
onCloseThe panel closes.
onMessageA message is added to the thread.ChatMessage
onCallStartA voice session starts.
onCallEndA voice session ends.CallSummary (sessionId, duration, status)
onErrorAn error occurs in the session.Error
SupportWidget.tsx · tsx
import { QalybWidget } from '@qalyb/widget';

export function SupportWidget() {
  return (
    <QalybWidget
      token="wgt_yourfulltoken"
      mode="voice"
      surface="voice-widget"
      onOpen={() => track('widget_opened')}
      onClose={() => track('widget_closed')}
      onMessage={(message) => track('widget_message', { role: message.role })}
      onCallStart={() => track('voice_call_started')}
      onCallEnd={(summary) =>
        track('voice_call_ended', {
          sessionId: summary.sessionId,
          duration: summary.duration,
          status: summary.status,
        })
      }
      onError={(error) => report(error)}
    />
  );
}
Note:
these callbacks are the client-side events available on the embed. For server-side notifications about calls and sessions — delivered to your backend over signed HTTP — use webhooks instead.

Restricting where the widget can load

Each widget token has an Allowed Domains list, set in the dashboard. Qalyb enforces it server-side: the request's Origin/Referer hostname must match an entry (exact or subdomain) or the API rejects it with Origin not allowed for this widget. Leaving the list empty allows any origin — fine for testing, but scope it to your real domains before launch so a leaked token can't be embedded elsewhere.

If a token leaks:
revoking a token deactivates it everywhere; deleting it permanently removes it and immediately disables the widget on every site where it is embedded. Both actions are in the widget's detail page and are admin-only. Rotate by creating a fresh widget and swapping the token in your embed.

Putting it together

Create the widget and copy the full token
In Deploy, create a Chat or Voice Widget, pick the agent, and copy the one-time token from the success dialog. Store it securely.
Add the script tag
Paste the <script> from the Embed tab before </body>, replacing the prefix placeholder with your full token.
Wire your own buttons (optional)
Hide the default launcher and call window.Qalyb.open(), toggle(), or sendMessage() from your page's controls.
Lock down Allowed Domains
Add your production hostnames so the token only works on your sites.
Verify on your site
Load the page, open the widget, and confirm the agent greeting and your branding appear.

Related guides

Was this guide helpful?