How can we help?

Tell us briefly what this is about - we will get back to you by email.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.
To the start page

Technical detail

Protocol, error codes, accessibility and the status behind every promise.

This page is for people who want the detail. Everything here is measured or read off the source — none of it is estimated.

How the widget loads

Two stages. First a small loader that draws the button and does nothing else. The chat itself is fetched only when one of five triggers fires — a click on the button, a command from the page, a rule, scrolling, or a timer.

There is a reason: most visitors never open the chat. Nothing should be loaded for them that they do not need.

For comparison, also measured: Crisp's loader is 2,876 B, Intercom's is 3,161 B. At 2,827 B we are level with them, not orders of magnitude below. Anyone claiming otherwise has not measured.

It does not disturb your site

The widget lives in a closed shadow DOM. Your styles cannot reach it, its styles cannot reach your page — not even if you set * { box-sizing: border-box } or use a CSS framework that redefines everything.

It runs under a strict Content Security Policy without a single console message: no eval, no web font, no tracking pixel. It sets its styles through constructable stylesheets, which style-src does not cover. The button, the chat window, product tours and the agent console on your site therefore need no unsafe-inline — measured under style-src 'self' and even under style-src 'none': zero violations, fully styled. Measured in current Chromium; older browsers without constructable stylesheets (such as Safari before 16.4) fall back to a <style> element, which a strict policy blocks.

Zero requests to other servers. Everything comes from the address you embed the widget from.

The protocol

The connection uses WebSocket. The protocol is versioned and defines **25 error codes** — each with a prescribed client response (retry, do not retry, close). An error not in the list is treated as "do not retry": better one message too few than an endless loop.

Reconnect with growing backoff. If the connection drops, messages go into a buffer and are sent as soon as it is back. Every message carries an id; the server recognises repeats and does not store them twice.

Accessibility

Built against WCAG 2.2 AA:

Contrast is calculated

You give a colour. The surface takes exactly that colour. The text on it is shifted until it reaches a ratio of 4.5:1 — the WCAG requirement for normal text.

Measured across 178 colours in two themes and three pairings, so 1,068 data points: not one below 4.5:1, the worst value 4.50:1.

That is the difference to other providers: there, a widget can be configured until nobody can read it.

Personal data

Masking happens in two places — in the visitor's browser before anything leaves the machine, and again on the server before anything is stored. Recognised are card numbers (with Luhn check), IBANs, e-mail addresses, phone numbers and long digit sequences.

Two places, because one is not enough: the browser one can be switched off, the server one only sees what arrives.

Rules

When the widget speaks up is defined in a declarative rule list — not in a condition in the code. Five rules ship by default (rage click, JavaScript error, purchase intent, exit intent, inactivity); your own can be added.

The difference is practical: changing a rule means changing a line, not a deployment.

Integrations

Webhook and Slack are done. For all others the provider is in the code, but most have never run against the real service.

Every integration runs through a queue with a circuit breaker: if a third party hangs, it is skipped for a while after repeated failures. A chat message is never held up by it — that is the most important rule in this layer.

What is not finished

So that nobody finds out while integrating:

Last updated: