🚧 Onboardics is in private beta while we finish privacy, billing, and reliability hardening.

Install

Paste this tag into your site's <head> (or anywhere before </body>). Replace onb_YOUR_KEY with the API key from your install page.

<script src="https://onboardics.com/v1.js"
        data-key="onb_YOUR_KEY"
        async></script>

That's it. The snippet auto-captures page views, clicks, rage clicks, scroll depth, session end, errors, and several other engagement signals listed below — no additional wiring required. For React/Next/Vue/Svelte/Shopify/WordPress/Webflow-specific instructions (including framework auto-detection), see the install page inside the dashboard.

Snippet size: <10 KB gzipped. Byte budget ceiling is 30 KB. Every commit that modifies the snippet reports the new size — see /security for the engineering standard.

Auto-captured events

The snippet emits these events without any additional code. All are subject to your setConsent() gate (below) — when consent is false, nothing is emitted.

Event typeWhen it fires
page_viewOn load + SPA navigation. Includes UTM, first-touch referrer, viewport, and device_type in metadata.
clickAny click on a button, link, or interactive element. Element selector, visible text (up to 80 chars), and x/y coordinates captured.
rage_click3 clicks within 500ms in a 30px radius. Signals UX friction.
scroll_depthMax % scrolled per page, bucketed 0/25/50/75/100. Emitted on pagehide.
time_on_pagePer-page duration emitted on pagehide + SPA nav.
visibility_changeTab visibility state transitions (hidden/visible).
idle2+ minutes of no activity. Capped at 3 per session.
session_endOn pagehide. Includes session_duration_seconds, page_count, event_count.
js_errorCaptures window.onerror + unhandledrejection. PII-redacted on-device (see setErrorCapture).
form_interactionFocus / blur / submit. Field names emitted, never field values. Denylist redacts password, ssn, cvv, card, token, etc.
identifyFired when identify() is called.
customAnything fired via track().
flow_*flow_impression, flow_step_view, flow_step_complete, flow_dismissal, flow_completion. Auto-emitted by the flow engine when in-app flows render.

Want to learn more about a specific event type or how these power audience segmentation? See engagement events and error capture.

identify(data)

Attach a user identity to the current session and all subsequent events. Call on login, signup, or any time you learn who the user is.

window.__onboardics.identify({
  email: 'user@example.com',
  userId: 'user_123',
  name: 'Jane Smith',
  plan: 'growth',
  signupDate: '2026-01-15',
  // ...any other properties
});

Validation: payload capped at 4 KB. Objects flattened at depth > 1. Functions and symbols rejected. Email format checked. Persists to localStorage as _ob_identity so identity survives page reloads until you call identify(null) or the user clears storage.

track(name, properties)

Emit a product-specific custom event. Use for behavior that matters to you but isn't auto-captured (form published, invite sent, payment completed, etc.). Once tracked, you can build AI-defined audience segments around these events.

window.__onboardics.track('form_published', {
  formId: 'f_abc123',
  fieldCount: 8,
  templateId: 'contact-form-v2'
});

Event name must be a string. Properties object is optional but recommended — it powers segment conditions like "users who called track('form_published') 3+ times."

setConsent(true | false)

GDPR/CCPA consent gate. When false, the snippet emits NO events — including errors. Default behavior honors navigator.globalPrivacyControl.

// User accepted cookies
window.__onboardics.setConsent(true);

// User declined, or opted out later
window.__onboardics.setConsent(false);

Termly is auto-detected. If you use Termly for consent management, you don't need to call setConsent() at all — the snippet reads Termly's analytics consent from localStorage on load and re-checks every 30 seconds. Cookiebot and OneTrust currently require manual wiring; auto-detection for those is on the roadmap.

setErrorCapture(true | false)

Opt out of automatic JS error capture. Default is on.

window.__onboardics.setErrorCapture(false);

Before any error data leaves the browser, a 9-pattern redaction chain strips Bearer tokens, API keys (sk_test_..., pk_live_...), URL query-string secrets, emails, UUIDs in URL paths, long numeric IDs, credit-card-like digit sequences, phone numbers, and Mac/Linux home-directory paths. Capped at 10 error events per session, deduplicated by message+file+line+column. Full PII-redaction spec at /security.

Privacy controls — element-level opt-outs

Two HTML attributes give your team fine-grained control over what visible text reaches the events table on click events. Both work on the clicked element OR any ancestor — set them once on a wrapping container and every descendant click is covered.

data-ob-mask="true"

Replaces the click target's visible text with the literal string [masked] in the captured event's metadata.text field. The click event still fires — you can still see "click on element X happened" in the Events stream — but the text content never leaves the browser. Useful for any element whose text content varies per user (account dropdown showing the user's email, order-history rows, partial credit-card displays, "Log out of session abc..." links).

<div data-ob-mask="true">
  <span>tyler@example.com</span>
  <button>Account settings</button>
</div>

Click on the button → click event captured, but metadata.text = [masked]. Both the email span text and the button label get masked because the button is a descendant of the wrapping div.

data-ob-ignore="true"

Suppresses the click event entirely. Nothing reaches the events table — no element selector, no text, no coordinates, no rage-click contribution. Useful for genuinely private surfaces where even "a click happened in this region" is information you'd rather not record (admin-only panels, security-sensitive flows like 2FA token entry, internal tooling embedded in customer-facing pages).

<div data-ob-ignore="true">
  <button>Reveal API key</button>
  <input type="text" value="sk_live_..." readonly>
</div>

Clicks anywhere inside this region are silently dropped at the snippet's click-handler boundary. Default-on automatic redaction (emails, JWTs, UUIDs, credit-card-like digit sequences, phone numbers, long bare numeric IDs) still applies on every other click — these two attributes give you stronger guarantees on top of the default redaction floor.

Both attributes walk up to 10 ancestors before giving up, so deeply nested elements inherit the opt-out without per-element annotation. Both are read fresh on every click — toggling them at runtime via el.setAttribute('data-ob-mask', 'true') takes effect immediately.

Snippet config flags

Four data-ob-* attributes on the snippet <script> tag let your team narrow what the snippet captures and renders without writing any JavaScript. Every flag is opt-in — absence equals today's default behavior. Set them once on the install tag and the policy applies to every page view across your site.

These flags layer on top of the per-element privacy controls (data-ob-mask and data-ob-ignore). The element-level attributes are surgical (one element, one decision); these snippet-tag flags are global (apply to every event the snippet emits). You can use both: per-element annotations stay in effect for the dimensions still being tracked.

data-ob-mode="tracking-only"

Disables flow rendering AND error capture AND the “Powered by Onboardics” badge in one toggle. Keeps every analytics event the snippet emits: page_view, click, scroll_depth, identify, time_on_page, session_end, visibility_change, idle, form_interaction. Use when you want analytics-only with no in-product UX and no overlap with your own error monitoring.

<script
  src="https://onboardics.com/v1.js"
  data-key="YOUR_KEY"
  data-ob-mode="tracking-only"
  async></script>

Tradeoff: no in-app guides will render even if you've authored flows in the dashboard. JS errors on your site will not flow into Onboardics. The free-tier badge (if your project is on Free) will not display.

data-ob-no-flows="true"

Disables flow fetch and render only. The snippet never makes a request to /api/flows; no tooltip, modal, banner, tour, or checklist will render. Everything else continues normally — analytics events, error capture, badge.

<script
  src="https://onboardics.com/v1.js"
  data-key="YOUR_KEY"
  data-ob-no-flows="true"
  async></script>

Tradeoff: any flow you've authored in the dashboard will not display on pages with this flag set. Use when your CSP blocks shadow DOM, when you want to test analytics in isolation, or when you have your own in-app guidance system and only want Onboardics for its diagnostic layer.

data-ob-no-error-capture="true"

Disables the snippet's window.addEventListener('error', ...) and unhandledrejection hooks. JS errors on your site will not be sent to Onboardics. Identical effect to the runtime window.__onboardics.setErrorCapture(false) call but locked at install time.

<script
  src="https://onboardics.com/v1.js"
  data-key="YOUR_KEY"
  data-ob-no-error-capture="true"
  async></script>

Tradeoff: you lose the JS error diagnosis surface in Onboardics. Use when you already have Sentry, Datadog, Rollbar, or another dedicated error monitoring tool and don't want overlap.

data-ob-no-text-capture="true"

Suppresses every human-readable text channel from /api/events payloads — click target visible text, accessibility labels (aria-label) inside element selectors, and authored flow names on flow_impression events. Coordinates, element identity (tag, id, class, href, type), event types, URL paths, and session metadata still flow.

<script
  src="https://onboardics.com/v1.js"
  data-key="YOUR_KEY"
  data-ob-no-text-capture="true"
  async></script>

This is different from the per-element data-ob-mask="true" attribute. data-ob-mask replaces a single element's text with the literal string [masked]; the click event still fires, you still see “a click happened on element X” in the Events stream, and only the text content is hidden. data-ob-no-text-capture is the global escape hatch — visible text content of every captured event is omitted entirely, no [masked] markers, just absent fields. Use the global flag when your compliance program requires that no visible-text content of any kind reaches an external service from any page on your site. Use the per-element attributes when you want surgical control over specific PII-bearing elements while preserving rich text capture elsewhere.

Combinations are valid. Setting both data-ob-mode="tracking-only" and data-ob-no-text-capture="true" disables flows + badge + error capture AND scrubs visible text from every analytics event. Setting data-ob-no-flows="true" and data-ob-no-text-capture="true" keeps error capture and the badge but disables flows and scrubs visible text. Order doesn't matter; flags compose.

A/B variant assignment

When you create an A/B test in the Flows dashboard, the snippet deterministically assigns each session to variant A or B based on a hash of session_id. Assignment is sticky across sessions via localStorage._ob_ab — a user who lands on variant A stays on A if they return from a new tab three days later.

Statistical validity of the two-proportion z-test isn't corrupted by cross-session variant flipping. Safe to run A/B tests without caveating "results may be noisy for returning users."

Debug mode

Turn on verbose logging to see exactly what the snippet is doing. Two ways:

// Query string (useful for sharing a debug link)
https://yoursite.com/?_ob_debug=1

// Programmatic
window.__onboardics.setDebug(true);

Every event emit, consent state change, flow render, identify/track call, and ingestion error will log to the console with a [onboardics] prefix. Safe to leave on in production — logs only, no extra network calls.

Pausing tracking

Every project admin can disable the snippet for their project from Settings → Pause tracking. Pausing flips a server-side flag; the snippet's next ingestion request (≤30 seconds) receives a kill signal and halts, and the decision is cached in each visitor's browser for one hour.

Reasons to pause:

Pausing does NOT delete existing data. It only stops new collection for the pause window. Resume anytime from the same section — tracking flows again on each visitor's next page load.

The pause control requires admin role (viewers and editors don't see it). Every tier has access including free.

Snippet behavior when paused: window.__onboardics.disabled === true for the session, localStorage._ob_kill holds the expiry timestamp. You can detect this state from your own code if you need to conditionally skip custom track() calls while the pause is in effect.

Pin a specific snippet version (SRI)

If your compliance program requires subresource integrity on third-party scripts, every snippet release is published at an immutable content-addressed URL with a SHA-384 integrity attribute. Fetch the current hash:

curl https://onboardics.com/v1/manifest.json

Paste the returned url and integrity into your install tag:

<script
  src="https://onboardics.com/v1/<hash>.js"
  integrity="sha384-<base64>"
  crossorigin="anonymous"
  data-key="YOUR_KEY"
  async></script>

Full SRI spec + the grace-period / breakage trade-offs at /security#sri.

React hook

The snippet is the SDK. Drop this 8-line hook into your codebase for a React-idiomatic wrapper — no package install required.

// onboardics.js — copy into your hooks folder
import { useCallback } from 'react';

export function useOnboardics() {
  const identify = useCallback((data) => {
    window.__onboardics?.identify?.(data);
  }, []);

  const track = useCallback((name, properties) => {
    window.__onboardics?.track?.(name, properties);
  }, []);

  return { identify, track };
}

Outgoing webhooks

Onboardics can POST signed HTTP requests to your endpoint when key events happen — activation, drop-off shifts, anomalies, briefing regeneration, or any custom event you define via /api/webhook-trigger. Wire into Zapier, n8n, Make, a custom Slack app, or your own backend.

Configure a webhook

Dashboard → Settings → Webhooks (admin role required). Paste an HTTPS URL, tick the event types you want, click Add. The secret is shown once at creation — save it to your environment.

Payload shape

Every delivery is a POST with JSON body:

{
  "event_type": "activation",          // one of: activation, drop_off_shift, anomaly,
                                       //   briefing_generated, test_event, or your custom type
  "project_id": "uuid",
  "emitted_at": "2026-04-19T17:42:00Z",
  "data": { /* event-specific payload */ }
}

Headers on every request:

Verify the signature

Before trusting any webhook payload, verify the HMAC matches. Reject any request where it doesn't.

Node.js:

const crypto = require('crypto');
function verifyOnboardicsWebhook(rawBody, signatureHeader, secret) {
  const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader || ''));
}

Python:

import hmac, hashlib
def verify_onboardics_webhook(raw_body: bytes, signature_header: str, secret: str) -> bool:
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature_header or '')

Ruby:

require 'openssl'
def verify_onboardics_webhook(raw_body, signature_header, secret)
  expected = OpenSSL::HMAC.hexdigest('SHA256', secret, raw_body)
  Rack::Utils.secure_compare(expected, signature_header.to_s)
end

Retries & auto-disable

We retry up to 3 times with exponential backoff (1s, 3s). Non-2xx responses count as failures. If a webhook hits 10 consecutive failures, we auto-disable it to protect your endpoint — re-enable from Settings once you've fixed the issue. Recent delivery history (last 20) is viewable per-webhook in Settings.

Custom events

Need to route an arbitrary event through Onboardics? POST to /api/webhook-trigger with a Bearer token (admin role) and a payload shaped like:

POST /api/webhook-trigger
Authorization: Bearer <supabase-jwt>
Content-Type: application/json

{
  "api_key": "onb_...",
  "event_type": "signup_completed",   // ^[a-z][a-z0-9_]{0,49}$
  "data": { "plan": "growth", "mrr": 199 }
}

Event type is validated (lowercase, alphanumeric + underscore, max 50 chars). Payload capped at 10KB. Rate limited to 1000 events/hour/project. Responds 202 Accepted immediately — delivery happens in the background.

More

Full API reference with every method, event property, and error code is coming soon. If you need detail on something not covered here, email us — we usually respond within 24 hours and will add it to this page.