Install the Cairn widget
An owner or administrator with settings.manage can install the widget without platform administrator access. Use Settings → Widget → Install in your own workspace. Copy its issued snippet, website ID, application address, CDN address and WebSocket address together. These values differ between environments; do not copy another workspace's installation or invent a CDN host.
Before installation
Choose the production workspace, identify the shared layout for each intended surface, and review the CSP guide. Cairn runs in your site's origin. Approved support content and ordinary account metadata are appropriate; PHI, payment credentials and regulatory identifiers are prohibited for the AccordRx pilot.
In Settings → Widget, configure allowed hosts before installing on a new domain. Enter each actual customer, seller and admin host, including staging only where approved. A configured allowlist rejects an unlisted page origin. Avoid broad wildcard rules. Keep local development separate from production.
Add the issued snippet
Place the two tags together before </body> in the shared layout. The globals tag must run before the asynchronous loader. Include the snippet once per document. The portal also provides instructions for a coding agent using these exact values.
<!-- Copy the complete production snippet from Settings → Widget here. -->
<!-- It defines window.$cairn, CAIRN_WEBSITE_ID, CAIRN_CDN,
CAIRN_APP_URL and, where configured, CAIRN_WS_URL before loading l.js. -->
The widget package is private to Cairn's monorepo. External customers install the issued script; npm install @cairn/widget and its React/Vue imports are not a public installation method.
Queue verified identity after the globals exist. The identity must come from your authenticated server session:
fetch('/cairn/identity', { credentials: 'same-origin', headers: { Accept: 'application/json' } })
.then(response => response.ok ? response.json() : null)
.then(identity => { if (identity) window.$cairn.push(['user:set', identity]); });
Your identity endpoint should return Cache-Control: no-store. Never put a signing secret or customer API key in a script tag, browser bundle, HTML data attribute, local storage, analytics event or source map.
Single-page navigation
Keep one widget instance in the application root. The current widget follows history and hash navigation and re-evaluates page targeting. If a framework replaces the entire body, the widget restores its root, although an experience already running during that replacement does not follow it; start it after navigation. Repeated removal of the root eventually stops restoration and logs a warning. Do not install again in each page component.
Refresh server-signed identity when an account changes. Do not mutate signed attributes from browser input. Finish logout with a full document navigation; an SPA route change leaves the old visitor in memory.
Sign out safely
Clear the visitor storage before your application's logout navigation:
for (const storeName of ['localStorage', 'sessionStorage']) {
try {
const store = window[storeName];
for (const key of Object.keys(store)) if (key.startsWith('cairn:')) store.removeItem(key);
} catch { /* Some browsers disallow storage. Continue the logout. */ }
}
window.location.assign('/logout'); // Your server must complete logout and redirect.
Adapt this to your framework's authenticated POST logout flow: clear storage, submit the CSRF-protected logout form, then let the server redirect with a full page load. Do not turn a POST-only logout route into a GET route to match this example.
Verify the installation
Use a browser on the actual allowed customer host, including mobile width. Check that l.js, manifest.json and the named application bundle load without CORS or CSP errors; the loader can try the previous bundle if the current bundle fails. Confirm a fresh widget session and its WebSocket connection succeed. A settings manager can inspect Installed in the portal after the widget boots.
Confirm the launcher icon, a published tour, an onboarding checklist and a support conversation. With a signed-in test account, confirm the contact contains only approved signed attributes and is marked verified. Sign out, then use a different account on the same browser: previous conversation history and onboarding progress must not carry across. A private or unlisted experience remains unpublished until you deliberately publish it.
Record the workspace, widget manifest version, host, browser and role, expected/actual results and sanitized screenshots. For AccordRx, repeat on customer, seller and admin layouts using the Laravel/Bagisto guide.
Troubleshooting
An empty install card means the environment has not issued a usable CDN configuration; contact support. An origin refusal means the allowed-host configuration and actual page host disagree. Styling failures require the complete CSP directives, including style-src-elem. Realtime failures require the issued WebSocket origin in connect-src. Identity refusals usually indicate a stale rotated secret, expired JWT, a mismatched email, or browser-supplied attributes. Report a sanitized error and workspace ID through the portal's support link; never send tokens or signing secrets.