Tools
Embed Script
The os.js script is the single entry point that powers analytics, widgets, and the SDK on your site.
Installation#
Add the script tag to every page where you want tracking and widgets. It loads asynchronously and won’t block page rendering.
<script src="https://operatorstack.dev/os.js" data-project="pk_xxxxx" data-chat="false" data-dev="true"></script>How it works#
When the script loads, it performs these steps in order:
- Reads the
data-projectkey from the script tag - Fetches your project configuration (cached for 5 minutes in localStorage)
- Starts tracking page views and events automatically
- Renders any widgets found on the page (Shadow DOM isolated)
- Exposes the
window.OperatorStackSDK - Resolves the
readypromise
Configuration caching#
Project configuration is cached in localStorage using a stale-while-revalidate strategy:
- Production: 5-minute TTL. Serves cached config immediately, fetches fresh in background.
- Dev mode: No caching. Always fetches fresh config.
Data attributes#
| Property | Type | Description |
|---|---|---|
| data-project * | string | Your project key (e.g., pk_xxxxx). Required. |
| data-api | string | Override the API base URL (for self-hosted deployments) |
| data-chat | string | Set to "false" to suppress the chat bubble on this specific page. Useful for pages where chat is distracting. (default: "true") |
| data-dev | string | Set to "true" for development mode: disables config caching, enables console logging. (default: "false") |
data-dev="true" to see configuration changes immediately without waiting for the cache to expire. Event batching#
Events are queued and sent in batches for performance:
- First batch sends immediately (via
setTimeout(0)to coalesce synchronous init events) - Subsequent events flush every 5 seconds
- On page unload or tab hide, remaining events are sent via
sendBeacon
Event types#
The following event types are tracked automatically or can be sent via the SDK:
| Property | Type | Description |
|---|---|---|
| page_view | auto | Fired on every page load |
| widget_impression | auto | Fired when a widget is rendered on the page |
| waitlist_signup | auto | Fired on successful waitlist submission |
| form_submission | auto | Fired on form submission |
| contact_message | auto | Fired on contact form submission |
| referral_conversion | auto | Fired when a referred visitor signs up |
| chat_opened | auto | Fired when chat bubble is opened |
| chat_closed | auto | Fired when chat window is closed |
| chat_message_sent | auto | Fired when visitor sends a chat message |
| console_error | auto | JavaScript errors from the host page (max 1,000/day) |
| performance | auto | Core Web Vitals (TTFB, FCP, LCP, CLS, INP) when performance monitoring is enabled |
| custom | SDK | Custom events via trackEvent() (max 10,000/day) |
Shadow DOM isolation#
All widgets render inside Shadow DOM, which means:
- Widget styles won’t leak into your page
- Your page styles won’t affect widget appearance
- Each widget is self-contained with its own scoped CSS
Widget HTML attributes#
Place these elements anywhere on your page. The embed script finds and replaces them with rendered widgets.
<!-- Waitlist signup form --><div data-os-widget="waitlist"></div> <!-- Contact form --><div data-os-widget="form" data-os-form="contact"></div> <!-- Custom form (by form key) --><div data-os-widget="form" data-os-form="frm_abc123"></div>Visitor identification#
OperatorStack uses a three-tier fallback strategy for visitor identification, with no cookies required:
Most stable identifier. A random UUID generated on first visit and stored in localStorage. Persists across sessions on the same browser.
SHA-256 hash of User-Agent, screen resolution, language, timezone, and hardware info. Stable across sessions and survives localStorage clearing. Does not rotate.
SHA-256 hash of IP + User-Agent + Accept-Language + month. Computed server-side as final fallback. Rotates monthly.
Do Not Track#
When a visitor’s browser sends the DNT: 1 header and your project has “Respect Do Not Track” enabled, the script skips all analytics tracking. Widgets and the SDK still function normally.
API endpoints#
/v1/projects/{projectKey}/configPublic/v1/projects/{projectKey}/events/batchPublic