Install
One script tag, one public key, and one line in an allowlist. The whole install is reversible from the dashboard, and nothing on this page needs a deploy once it is done.
Four steps#
- 01
Copy your website key
In the dashboard, open
Websites, pick the site, and copy the key underWebsite key. It starts withwk_.The key is public and belongs in your page source. It identifies a website; it does not authenticate anyone. Nothing sensitive is reachable with the key alone.
- 02
Paste the snippet before </body>
The same snippet, pre-filled with your key, is on the website’s dashboard page under
Install the widget.the embed snippet<script> window.$connect = window.$connect || []; window.CONNECT_WEBSITE_KEY = "wk_xxxxxxxxxxxxxxxxxxxxxxxx"; // optional: window.CONNECT_TOKEN_ID = "<per-user token from your backend>"; (function () { var s = document.createElement("script"); s.src = "https://<your-connect-host>/widget/v1.js"; s.async = 1; document.head.appendChild(s); })(); </script> - 03
Allow your site’s origin
Add every origin the widget will run on to
Allowed originson the same dashboard page. Until you do, the widget refuses to boot. See Allowed origins for the exact format — this is the step that trips people up. - 04
Reload and send a message
The launcher appears in the bottom corner. Send yourself a message and watch it arrive in the dashboard inbox.
Allowed origins#
Connect checks the origin of the page the widget is embedded in, and boots only for origins on the website’s list. An origin that is not on it gets a 403 and the widget quietly does nothing.
The match is an exact string comparison, which means:
- Scheme, host, and port, and nothing else. No path, no trailing slash.
https://acme.com/does not matchhttps://acme.com. https://acme.comandhttps://www.acme.comare different origins. So arehttp://andhttps://, and so is every subdomain. List each one.- A non-default port is part of the origin:
http://localhost:5173, nothttp://localhost. - An empty list allows nothing. A brand-new website boots nowhere until you add the first origin.
https://acme.com
https://www.acme.com
https://shop.acme.com
http://localhost:5173Content-security policy#
If your site sends a Content-Security-Policy header, three directives need to know about your Connect host.
- script-srcrequired
- Your Connect host, so the loader at /widget/v1.js can run.
- frame-srcrequired
- Your Connect host, so the chat window at /widget/frame.html can be framed. Some older browsers read
child-srcinstead. - img-srcrecommended
- The launcher shows the available agent’s avatar, and that image is loaded by your page. Allow
https://gravatar.com, plus wherever your team’s profile pictures are hosted if they signed in with Google or uploaded their own. Without it the launcher falls back to its icon — the widget still works.
Content-Security-Policy:
script-src 'self' https://connect.example.com;
frame-src https://connect.example.com;
img-src 'self' data: https://gravatar.com;Single-page apps#
Nothing to do. The loader wraps history.pushState and listens for popstate, so client-side route changes are reported to Connect on their own and the agent sidebar tracks the visitor’s current page through the whole session.
Install the snippet once in your app shell. Do not add or remove it per route: a second injection is ignored, but tearing the script out mid-session drops the visitor’s live connection.
Signed-in visitors#
Set window.CONNECT_TOKEN_ID to a stable, unguessable value your backend mints for the signed-in user, and Connect maps it to the same visitor record on every device and browser they use. Without it, a visitor is anonymous per browser and a phone and a laptop look like two people.
<!-- rendered by your server, for a signed-in customer -->
<script>
window.$connect = window.$connect || [];
window.CONNECT_WEBSITE_KEY = "wk_xxxxxxxxxxxxxxxxxxxxxxxx";
window.CONNECT_TOKEN_ID = "usr_9f2a…"; // stable, unguessable, per user
// …loader snippet…
</script>Knowing when it is ready#
You never have to wait. $connect starts life as a plain array, so commands pushed before the loader arrives are queued and replayed in order the moment it does. Push identity and session data straight from your page — no readiness check, no polling.
If you do need the moment the queue drains, define window.CONNECT_READY_TRIGGER before the snippet.
window.CONNECT_READY_TRIGGER = function () {
// The queue has been swapped for the live object and every
// command pushed before boot has run.
$connect.push(["do", "chat:open"]);
};What it stores in the browser#
Useful if you maintain a cookie or privacy page. Connect sets no cookies. It uses localStorage in two separate places.
On your origin#
- connect_launcher_<website-key>
- The launcher’s last known colour, icon, and side. Cached so the button paints in your brand colour immediately instead of flashing the default green while the widget boots. No visitor data.
Inside the chat iframe, on the Connect origin#
- connect_anon_id
- The anonymous visitor id, so a returning visitor keeps their conversation.
- connect_geo
- A cached copy of the coarse location shown to your agents.
- connect_appearance_<website-key>
- The resolved palette, so the chat window paints before boot.
- connect_nudge_shown
- Marks that this visitor already saw the first-visit nudge, so it fires once.
- connect_widget_sound
- The visitor’s own choice about the notification chime, on or off.
Because that second set lives on the Connect origin rather than yours, it is partitioned per site by the browser and is not readable from your page.
Confirming it worked#
- The launcher is in the corner and opens a chat window.
window.__CONNECT_LOADED__istruein the console.$connect.push(["do", "chat:open"])in the console opens it.- A message you send appears in the dashboard inbox within a second.
If any of those fail, Troubleshooting lists what each symptom means.