Install Cortality in about ten minutes.
Two script tags, one attribute on your primary call-to-action, and a console message that tells you it worked. Every step below is self-verifying, and this page is written so you can forward it directly to whoever deploys your site.
Before you start: get your siteId
Sign in at app.cortality.com. The setup screen shows your siteId and the tag, ready to paste. If you don't have an account yet, email colton@cortality.com; we typically set it up the same day.
Quickstart
Paste this immediately before your page's closing </body> tag, using the siteId from your setup screen:
<script src="https://cdn.cortality.com/c.js" async></script>
<script>
window.addEventListener('load', function () {
if (window.Cortality) {
Cortality.init({
siteId: 'op_yourcompany',
endpoint: 'https://ingest.cortality.com/v1/sessions' // stated explicitly so installs never depend on defaults
});
}
});
</script>
siteId. When the page loads, the script gets a short-lived signing key that works only from your registered domain, so copying the tag to another site sends nothing. Every submission is verified and rate-limited on our side.Install it exactly as written. Don't add lazy-loading, interaction-gating, or "load after the user does something" wrappers, and don't move it inside a component or useEffect. Deferred loading blinds the measurement to the first seconds on the page, which is exactly what we measure. The load hook in our block is fine and expected: the script loads async in parallel with your page, and the hook just guarantees it's ready before init. If your page is very media-heavy and you want capture armed even earlier, ask us for the early-boot variant (the same init call placed right after the script tag with a brief readiness poll, so it doesn't wait for the load event). Exception: if you use a consent tool, load or initialize Cortality only after consent. That is supported and expected (see disclosure duties).
If anything mangles the copy-paste, the two hostnames are cdn.cortality.com (script) and ingest.cortality.com (endpoint).
Where the tag goes, by stack
| Stack | Placement |
|---|---|
| Static HTML | In each page you want measured, before </body>. |
| Astro / Next.js / layouts | In the shared layout file so every page carries it, before </body>. |
| Vite / React / Vue SPA | In the root index.html, before </body>. If the same app serves logged-in areas, add the route allowlist. |
| WordPress | Appearance → Theme File Editor → footer.php before </body>, or (easier and update-safe) a header/footer snippet plugin such as WPCode: paste into the "footer" slot. |
| Webflow | Site settings → Custom Code → Footer Code, then publish. |
| Shopify | Online Store → Themes → Edit code → layout/theme.liquid, before </body>. Note: this covers your storefront pages; Shopify's checkout pages aren't editable on standard plans, so tag an on-page action (add-to-cart, a lead form) as your conversion rather than the checkout button. See conversion tracking. |
| Google Tag Manager | Supported if you can't touch code at all: a Custom HTML tag firing on All Pages, containing the full Quickstart block. Direct install is preferred (GTM adds its own loading layer ahead of ours), but a GTM install measures correctly. |
| AI site builders (Lovable, v0, Bolt…) | Paste the prompt below into the builder's chat. |
| Agency-built / not sure | Forward this page to whoever deploys your site; the Quickstart plus one row above covers them. If nobody can make the change, email us and we'll walk you or your agency through it. |
Prompt for AI site builders
If your site is built and edited through an AI tool, paste this to it, with your real values substituted:
Add Cortality analytics. In index.html, immediately before the closing
body tag, add exactly this, and do not wrap it in a React component,
useEffect, or any loader:
<script src="https://cdn.cortality.com/c.js" async></script>
<script>window.addEventListener('load',function(){if(window.Cortality){
Cortality.init({siteId:'YOUR_SITE_ID',
endpoint:'https://ingest.cortality.com/v1/sessions'});}});</script>
Then add the attribute data-cortality-conversion to our primary
call-to-action button, passing it through to the real DOM element.
If index.html or hosting config sets a Content-Security-Policy, add
https://cdn.cortality.com to script-src. Do not modify the snippet
in any other way.
Verify it works
- Open your site in a normal browser window.
- Right-click → Inspect → Console.
- Look for:
[Cortality] session started
If the message doesn't appear
- A CSP error in the console mentioning cdn.cortality.com → see Content Security Policy.
- No error at all → the tag likely isn't in the served HTML. View source and search for
cortality; if it's missing, the change didn't deploy. [Cortality] path not in allowedPaths→ working as configured; you're on a route outside your allowlist.- Anything else → email colton@cortality.com; we answer same-day.
Conversion tracking
Add one attribute to the element that counts as the win on your page: the form submit button, the booking button, the sign-up link.
<button data-cortality-conversion>Request a Demo</button>
In React and similar frameworks, make sure the attribute reaches the real DOM element. On a plain <button> it passes through automatically; on a custom component, the component must spread extra props onto its underlying element. On visual builders (Webflow, WordPress page builders), use the element's custom-attribute setting; if your builder doesn't expose one, tell us which button is the win and we'll suggest the cleanest alternative for your setup.
SPA route allowlist
Single-page applications serve everything from one index.html, so a root-level tag would execute on every route, including logged-in product areas. If your app has authenticated routes, restrict collection to your public marketing routes with allowedPaths:
Cortality.init({
siteId: 'op_yourcompany',
endpoint: 'https://ingest.cortality.com/v1/sessions',
allowedPaths: ['/', '/pricing', '/about', '/blog/*']
});
The behavior is fail-closed in both directions:
- Landing outside the list (a deep link into the app, a bookmark, a login page): the snippet initializes nothing. No session, no listeners, no collection.
- Navigating from a listed route into an unlisted one (marketing page → logged-in app): the snippet sends what it collected on the marketing page, then permanently stops all collection for the rest of that browser tab's life. It does not re-arm if the visitor returns to a marketing route.
How it detects navigation: the snippet monitors the browser's history API (pushState, replaceState, and back/forward), which is how SPA routers change routes without reloading, so client-side route changes are caught without any integration work in your router.
Matching rules: entries are pathnames. Exact entries match exactly (trailing slashes tolerated). A trailing * matches a whole section: '/blog/*' covers /blog and every post under it. Anything not listed is out of scope by default, so new product routes are excluded without you maintaining a blocklist.
data-cortality-ignore or call Cortality.disable() on login (below).Programmatic stop
Belt and suspenders for auth boundaries or consent controls: calling Cortality.disable() immediately and permanently stops collection for the tab and sends what the current page has collected. Call it from your sign-in handler if you want a hard guarantee independent of routing:
onLogin() {
window.Cortality && Cortality.disable && Cortality.disable();
}
[Cortality] session started. Navigate into your app and it prints [Cortality] capture stopped: left allowed routes. Land directly on an app route and it prints [Cortality] path not in allowedPaths, capture off.Excluding elements
Mark any element with data-cortality-ignore and it is excluded from observation, along with everything inside it:
<div data-cortality-ignore>
<!-- live chat widget -->
</div>
Use this for anything that renders visitor-entered or personalized content outside a form field. Automatic masking covers form input values everywhere, but content your page renders elsewhere, a live-chat transcript, a "welcome back" banner, a quote-calculator result, is page content and will be captured unless you exclude it. Chat widgets are the most common case, and they are yours to mark, including any you add later. See the masking model for exactly what is and isn't covered automatically.
Content Security Policy
If your site sends a Content-Security-Policy header, two directives matter:
| Directive | What to add | When |
|---|---|---|
| script-src | https://cdn.cortality.com | Always (loads the snippet) |
| connect-src | https://ingest.cortality.com | Only if your policy doesn't already allow https: |
One subtlety: the capture module fetches your page's images to include them in page captures. If your images are served from a different origin (a CDN or image host), that origin needs to be in connect-src too, unless https: is already allowed. If you're not sure where your CSP is configured, it's usually your hosting config or server middleware; send us the header and we'll tell you the exact change.
Your disclosure duties
Before or alongside install: disclose this technology in your privacy policy, and evaluate whether your jurisdictions require visitor consent. Some US state laws treat recording website interactions as requiring disclosure or consent, independent of cookies. If you gate on consent, call Cortality.disable() when a visitor declines.
The authoritative version of this guidance, including a starter policy clause you can hand your counsel and when to ship it, lives in the Trust Center: Your obligations as a client.
Your dashboard
Your dashboard lives at app.cortality.com. Sign in with your email. Use "Forgot password?" to reset your password. Invite teammates from Settings; each login sees only the sites it belongs to. Sessions typically appear within about a minute of a visit ending. The dashboard shows your page section-by-section with attention scores, your conversion funnel, and the current recommendations; your written reports are built from the same data.
What gets collected
The short version: mouse, scroll, touch, and click behavior with page coordinates; periodic images of the page as the visitor's browser rendered it; a sanitized copy of the page's HTML; and basic session context. No cookies, no keystrokes, no form values. Linking a visitor's visits is off by default; a site can turn it on for purchase attribution, using a random ID in browser local storage. The standard tag never uses the camera; gaze measurement is a separate module that loads only on sites that opt in.
The complete, versioned statement, including the masking model and its limits, IP handling, retention, and subprocessors, lives in the Trust Center. That page is the authoritative reference; this section is only a summary.
Removing the snippet
Remove the two script tags. Collection stops immediately. No cookies were set, and the crash-protection buffer in visitors' browsers clears itself. If your site turned on visit linking, that random ID stays in visitors' local storage but is no longer read. Data already collected is kept as described in Retention.
FAQ
Will it slow my site down?
The loader is about 25 KB compressed and loads async, so it never blocks rendering. Capture work is throttled and deferred to idle time. A second module (about 10 KB) lazy-loads only when the first page capture is needed.
Can it break my page?
The snippet runs inside error isolation: any internal failure disables the snippet silently rather than affecting your page. It does not alter your page's visible content, styling, or user-facing behavior. To do its work it adds a small number of non-rendered elements of its own (for example, injecting its lazy-loaded capture module as a script tag), but it never edits, removes, or restyles anything of yours.
When do sessions show up?
Data ships during the visit and when the visitor leaves; sessions are typically queryable in your dashboard within about a minute of the visit ending.
How does this interact with consent banners and recording-consent laws?
The snippet sets no cookies, but it does use browser storage, and some states' laws treat recording website interactions as requiring disclosure or consent regardless of cookies. We recommend disclosing the tool in your privacy policy and evaluating consent requirements with your counsel; see your disclosure duties. If you gate on consent, initialize Cortality only after acceptance, or call Cortality.disable() on decline.
Can we run it on multiple domains?
Yes; each domain gets its own siteId so data stays separated. Email us the domains and we'll set them up.
Anything we should avoid during a measurement window?
Keep the page layout stable while a measurement runs; a mid-week redesign resets the comparison. Tell us when you ship a change and we'll timestamp it to measure before versus after.
Mobile visitors?
Captured fully: touch, scroll, and page captures. Cursor-specific metrics (hover time) exist only on desktop, since phones have no cursor; scroll-reach and funnel numbers include all devices.
