Drive the widget from your page: open and close it, send messages, identify signed-in users, track conversions, and react to lifecycle events.
Once the widget script loads it registers a global at window.CompaninWidget. It gives your page a small, dependency-free API to open and close the widget, send messages, identify the current user, and subscribe to lifecycle events — without coupling your code to the widget’s internals.
The script loads asynchronously, so the global appears a moment after your page does. Guard your first call (window.CompaninWidget?.open()), or subscribe to the widget.ready event. When you embed more than one widget on a page, each instance is also registered by its data-instance-id under window.CompaninWidgets — use CompaninWidgets.get(‘id’) to target a specific one; window.CompaninWidget points at the most recently created instance.
Call these on window.CompaninWidget once the widget has loaded. Methods that take no effect before the iframe is ready fail quietly and log to the console rather than throwing, so a mistimed call never breaks your page.
open() / close() / toggle() — Expand, collapse, or flip the chat panel.show() / hide() — Show or hide the whole widget container, launcher included.isOpen() / isVisible() / isReady() — Read the current panel, container, and bootstrap state.sendMessage(text) — Send a message as the visitor and get a reply.prefill(text) — Pre-populate the input without sending, so the visitor can edit first. Call before open().identify(user) — Attach a user identity to the session — { userId, email, name, metadata, token }. See below.setContext(data) — Push page-level context; the widget merges it into the next request’s page_context.setTheme(theme) — Switch palette at runtime — 'light', 'dark', or 'system'. Overrides the dashboard theme and data-theme.trackConversion(goal, value, opts) — Record a goal reached after a chat, e.g. trackConversion('checkout', 79.00, { currency: 'EUR' }).beforeSend(fn) / afterReceive(fn) — Inspect, rewrite, or cancel messages on their way out or in. Return null to cancel.on(event, handler) — Subscribe to a lifecycle event; returns an unsubscribe function. off(event, handler) also works.update(config) — Live-apply a partial widget configuration without reloading the page.reset() — Clear the current conversation and start a fresh session.enableDebug() / disableDebug() — Turn verbose console logging on or off on a live page.grantConsent() / revokeConsent() — Tell the widget whether it may use browser storage. Required for consent-gated deployments.getVersion() / destroy() — Read the loader version, or tear the widget down and remove it from the page.<script> // The embed loads asynchronously — wait for it before calling in. window.CompaninWidget?.on('widget.ready', function () { var w = window.CompaninWidget; w.open(); w.prefill('I have a question about pricing.'); console.log('version', w.getVersion(), 'open?', w.isOpen()); });</script>Let the widget recognize your signed-in users so conversations are personalized and restored across their devices. Your server signs a short-lived token that the widget hands to Companin; Companin verifies it and links the session to that user.
1. Get your signing secret from the dashboard and copy it into your server’s environment. Never expose it in browser code.
2. Sign a token on your server — a short-lived HS256 JWT carrying the user's id (sub), email and name:
const jwt = require('jsonwebtoken'); // npm i jsonwebtokenfunction signUserToken(user) { return jwt.sign( { sub: String(user.id), email: user.email, name: user.name }, process.env.COMPANIN_EMBED_SECRET, { algorithm: 'HS256', expiresIn: '5m' } );}3. Hand the token to the widget. On a server-rendered page, add it to the script tag as data-user-token; in a single-page app, call identify() after the user logs in:
<!-- Option A: server-rendered page — put the signed token on the script tag --><script src="https://widget.companin.tech/widget.js" data-widget-key="wgt_your_key" data-user-token="SERVER_SIGNED_JWT"></script><!-- Option B: after login — fetch a fresh token and call identify() --><script> fetch('/api/widget-user-token') .then(function (r) { return r.json(); }) .then(function (data) { if (data.token) window.CompaninWidget.identify({ token: data.token }); });</script>A bad or expired token is ignored — the widget simply stays anonymous, so it is safe to always attempt identification.
beforeSend(fn) runs your function on every outgoing visitor message before it reaches the API; afterReceive(fn) runs on every agent reply before it renders. Return a modified string to change it, or return null to cancel. Both accept a Promise, so you can call your own service first, and multiple interceptors run in registration order.
// Redact email addresses before a message leaves the browser.window.CompaninWidget.beforeSend(function (message) { if (!message.trim()) return null; // return null to cancel the send return message.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, '[email]');});// Post-process the agent's reply before it renders.window.CompaninWidget.afterReceive(function (reply) { return reply.replace(/support@example\.com/g, 'our support team');});Call trackConversion() when a visitor reaches a goal so the dashboard can attribute it to the conversation that helped. Pass a goal slug and, when there is one, a monetary value and currency. Common slugs are add_to_cart, checkout, signup, lead and booking, but any slug you choose is accepted. Pass a dedupKey (or a metadata.order_id) so a reload cannot double-count the same order.
// On your order-confirmation page:window.CompaninWidget?.trackConversion('checkout', 79.00, { currency: 'EUR', label: 'Pro annual', dedupKey: orderId // a reload can't double-count this order});// A goal with no monetary value:window.CompaninWidget?.trackConversion('signup');Subscribe with CompaninWidget.on(name, handler), which returns an unsubscribe function. Handlers receive an envelope: { event, timestamp, data, context } — the payload is on envelope.data. Late subscribers immediately receive the last envelope for that event, so you never miss one by attaching after load.
widget.ready — The iframe finished its bootstrap handshake and the API is live.open / close — The chat panel was expanded or collapsed. Aliases: widget.opened, widget.closed.message — The visitor sent a message. Alias: message.sent.response — The agent replied. Alias: message.received.conversation.created / conversation.closed — A conversation started or ended.user.updated — identify() was called or the verified user identity changed.file.uploaded — The visitor attached a file.conversion.tracked — trackConversion() recorded a goal.theme.change — The active light/dark palette changed.authFailure — Authentication with the backend failed. Alias: auth.failed.error — The widget reported an error.var widget = window.CompaninWidget;widget.on('message', function (e) { console.log('Visitor sent:', e.data);});widget.on('response', function (e) { console.log('Agent replied:', e.data);});var stopWatching = widget.on('authFailure', function (e) { console.warn('Widget auth failed:', e.data);});// on() returns an unsubscribe function.stopWatching();Every lifecycle event is also dispatched on window as a CustomEvent named companin-widget:<event> — for example companin-widget:response. The same envelope is on event.detail. Use these when the listening code cannot hold a reference to the widget, such as an analytics tag or a tag-manager snippet.
// Note the hyphen: companin-widget:<event>, not companin:widget:<event>.window.addEventListener('companin-widget:response', function (e) { // e.detail is { event, timestamp, data, context } console.log('Agent replied:', e.detail.data); console.log('on page:', e.detail.context.pagePath);});window.addEventListener('companin-widget:open', function () { window.dataLayer?.push({ event: 'widget_open' });});