Drive the widget from your page and keep every conversation resilient — automatic reconnect, message retry, offline detection, timeouts and friendly error handling.
Once the widget script loads, Companin registers a host bridge at window.CompaninWidgetHost. It gives your page a small, dependency-free API to open and close the widget, send messages, read state, and subscribe to lifecycle events — without coupling your code to the widget's internals.
The bridge also adds a reliability layer on top of the widget: messages are queued while offline, retried automatically when the connection returns, timed out if no response arrives, and the widget auto-reconnects after transient errors. Every state change is surfaced as a DOM event so you can react in your own UI.
Call these methods on window.CompaninWidgetHost after the widget has loaded. They are safe no-ops when the widget is not ready yet, so you never need to guard every call.
open() / close() / toggle() — Show, hide or flip the widget panel.sendText(text) — Send a plain-text message as the visitor.sendPayload(payload) — Send a structured message or command object.sendSafe(payload) — Send through the reliability layer — queued when offline and passed through any interceptors.sendWithTimeout(payload, ms) — Send and emit a timeout event if no response arrives within ms (default 10000).intercept(fn) — Register a function that can rewrite or cancel outgoing messages; returns an unsubscribe function.getState() — Read the current host state: open status, last sent and received message, online flag and command history.getIsOnline() — Whether the host currently considers the connection online.drainRetryQueue() — Manually flush any messages queued while offline.cleanup() — Unsubscribe every listener — call before removing the widget.<script> window.addEventListener('load', function () { var host = window.CompaninWidgetHost; if (!host) return; host.open(); host.sendText('Hi! I have a question about pricing.'); console.log(host.getState()); });</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. In the dashboard, open Install → Logged-in users → Generate secret, then 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://YOUR_WIDGET_HOST/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.
Use sendSafe instead of sendText when delivery matters. The host watches the browser's online and offline events; while offline, messages are added to a retry queue and a companin:widget:offline event fires. As soon as the connection returns, the queue is drained automatically in order and companin:widget:online fires.
window.CompaninWidgetHost.sendSafe('Track my order #1234');window.addEventListener('companin:widget:offline', function () { showBanner('You are offline — your message will send automatically.');});window.addEventListener('companin:widget:online', function () { showBanner('Back online.');});window.addEventListener('companin:widget:retryDrained', function (e) { showBanner(e.detail.count + ' queued message(s) sent.');});Wrap a send in sendWithTimeout to guard against a response that never arrives. If no reply is received within the timeout (10 seconds by default), a companin:widget:timeout event fires so you can show a friendly retry prompt instead of leaving the user waiting.
window.CompaninWidgetHost.sendWithTimeout('Are you there?', 8000);window.addEventListener('companin:widget:timeout', function () { showRetryPrompt('That took longer than expected. Try again?');});When the widget reports an error, the host attempts to reconnect up to three times with an increasing back-off (1.5s, 3s, then 4.5s), emitting companin:widget:reconnecting on each attempt. A successful response resets the counter; if all attempts fail, companin:widget:reconnectFailed fires so you can fall back gracefully.
window.addEventListener('companin:widget:reconnecting', function (e) { console.log('Reconnecting — attempt', e.detail.attempt, 'in', e.detail.delay, 'ms');});window.addEventListener('companin:widget:reconnectFailed', function () { showBanner('We could not reconnect. Please refresh the page.');});Register an interceptor with intercept(fn) to inspect, rewrite, or cancel every outgoing message. Return a modified payload to change it, return false to cancel the send, or return nothing to let it through unchanged. intercept returns an unsubscribe function.
const stop = window.CompaninWidgetHost.intercept(function (payload) { if (typeof payload === 'string') { if (!payload.trim()) return false; // cancel the send return payload.replace(/[\w.+-]+@[\w-]+\.[\w.-]+/g, '[email]'); } return payload;});// Later, to remove the interceptor:stop();The host re-emits every widget lifecycle change as a DOM CustomEvent on window, so you can react without holding a reference to the widget. The relevant data is on event.detail.
companin:widget:open / close — The widget panel was opened or closed.companin:widget:message / response — The visitor sent a message, or the agent responded.companin:widget:authFailure — Authentication with the backend failed.companin:widget:error — The widget reported an error.companin:widget:offline / online — The browser lost or regained its connection.companin:widget:queued / retryDrained — A message was queued while offline, or the queue was flushed.companin:widget:reconnecting / reconnectFailed — An automatic reconnect attempt started, or all attempts were exhausted.companin:widget:timeout — A message did not receive a response within its timeout.window.addEventListener('companin:widget:message', function (e) { console.log('Visitor sent:', e.detail.message);});window.addEventListener('companin:widget:response', function (e) { console.log('Agent replied:', e.detail.response);});window.addEventListener('companin:widget:authFailure', function (e) { console.warn('Widget auth failed:', e.detail.error);});Decoupled code can drive the widget by dispatching a companin:widget:command event instead of calling the API directly. Send a string to deliver a message, or a detail object such as { action: 'open' } or { text: 'Hello' }. This is handy for analytics tags, GTM, or other scripts that should not import the widget.
// Open the widget from anywhere — no widget reference needed.window.dispatchEvent(new CustomEvent('companin:widget:command', { detail: { action: 'open' }}));// Or send a message.window.dispatchEvent(new CustomEvent('companin:widget:command', { detail: { text: 'I need help with billing' }}));Set window.__COMPANIN_WIDGET_WEBHOOK_URL to a collector endpoint and the host forwards open, close, message and response events to it as JSON via navigator.sendBeacon (falling back to a keepalive fetch). This is a lightweight, client-side complement to server-side webhooks — useful for first-party analytics.
// Point the host at your collector before the widget loads.window.__COMPANIN_WIDGET_WEBHOOK_URL = 'https://example.com/collect/widget';