Script and tracking options
Use the generated snippet in Settings → Installation. The tracking token identifies a website; it is not an account password or order API secret.
Two snippet styles
Settings → Installation → Scripts offers two styles of the same tracker:
- Minimal - one script tag with
data-siteanddata-apionly. The tracker asks/api/collect-configfor saved dashboard options before measuring. It queues up to 50 pageviews or explicit events for at most two seconds, preserving their navigation paths. Success applies the options before sending; failure or timeout uses defaults with cross-domain handoff off. A late response is ignored. Change an option in the dashboard and pages pick it up on their next load. - Explicit - the options are
data-*attributes on the script tag (table below). As soon as any ofdata-utm,data-hash,data-vitals,data-conversions,data-debugordata-domainsis present the tracker ignores the dashboard options entirely and, unlessdata-conversions="true"is set, never requests the configuration (data-shopifyanddata-localdo not switch modes).
Dashboard options are saved per website with Save tracking options; the website owner (and any teammate with the admin role) can change them. Browsers with Do Not Track or Global Privacy Control receive an empty configuration and send nothing either way.
Script attributes
| Attribute | Default | Effect |
|---|---|---|
data-site |
required | Public website collection token. |
data-api |
required | Collection origin without a trailing slash; the tracker appends /api/collect. |
data-local |
absent | Also collect on localhost and 127.0.0.1. |
data-conversions="true" |
off in raw HTML | Load the enabled automatic conversion presets once per document. New websites enable it in dashboard configuration; generated explicit snippets include the attribute while it is on. |
data-hash="true" |
off | Count location.hash changes as pageviews and report the path with its fragment, for example /docs#install. |
data-utm="off" |
on | Stop reading all five UTM fields: source, medium, campaign, term and content. |
data-vitals="off" |
on | Skip Web Vitals measurement entirely (no performance observers). |
data-debug="true" |
off | console.debug each payload before it is sent. Safe when the console is unavailable. |
data-shopify="off" |
on | On Shopify Online Store themes the tracker writes its attribution into the cart attributes automatically (Shopify revenue guide); off leaves the cart untouched. Pages without window.Shopify are never affected. |
data-domains="a.com,b.com" |
off | Cross-domain handoff: links to the listed hosts (and their subdomains) carry the visitor ID, and a landing page adopts an incoming ID. |
Explicit attributes are read once when the script runs. Omit an attribute to keep its default; the Explicit snippet in Settings → Installation → Scripts only adds the attributes whose dashboard value differs from the default.
Keep the generated data-api value (https://app.seean.io for production); omitting it sends to the host website instead. Conversion rules load once per document; reload after changes. defer loads the script without blocking HTML parsing. Save your choices under Settings → Installation → Dashboard, then copy the Explicit snippet from the Scripts tab; unsaved toggles do not change the snippet.
Navigation
The tracker sends an initial pageview, observes history pushState, replaceState and popstate, and handles restored pages. It deduplicates consecutive page notifications for the same session and path. Query strings are never part of the path. Hash fragments are ignored by default; with data-hash="true" the tracker also listens to hashchange and reports /docs#install as the path, so hash-router applications get one pageview per route. A changed tagged campaign or expired session can start a new session and count the same pathname again; returning from another pathname also counts a pageview.
Install one classic external script in the shared document. The same-path deduplication happens within one tracker instance; it does not make loading two copies safe. Do not reload the script or call track("pageview") on every router change. This can add duplicate pageviews. For framework placement, use the installation guides.
Presence on an open page
The tracker sends a small presence signal every 30 seconds while the page is visible, independently of Web Vitals and automatic conversions. It resumes when the tab becomes visible again. These signals keep a visitor in Realtime without adding pageviews, events, usage or analytics-session activity. Hidden or sleeping tabs stop sending; the two-minute online window and dashboard refresh determine when they disappear. No snippet change is required; existing open tabs receive the updated script on their next reload. The unversioned tracker is served with Cache-Control: no-store on standard, first-party and custom collection hosts. If a browser retained an older version under a previous cache policy, use a hard refresh to fetch the update.
Loading and defaults
Keep data-site and data-api on the script element that loads k.js. The tracker reads that element when it executes. Importing the file as an ES module or fetching its text and evaluating it is not a supported installation.
Pageviews, all five UTM fields and Web Vitals are on by default; hash views, cross-domain handoff and debug logging are off. Section visibility events require attributes on the sections you choose. Web Vitals availability depends on the browser and actual page activity. Debug logging prints payloads before sending; a missing console never breaks collection.
Public methods
window.seean.track(name, properties) sends a custom event. window.seean.attribution() returns visitor and landing/converting path information for a server-side order integration.
After the script loads, await window.seean?.ready waits for the first collection attempt to finish (up to five seconds after sending, plus configuration loading). Then read attribution when checkout starts. ready is not proof of stored traffic: blocking, opt-out or a failed request can leave visitorId absent. Missing attribution must never block payment. The visitor ID is scoped to a UTC day; Seean does not link future daily identities to it.
Call these methods only after the tracker has loaded. Optional chaining can avoid an exception if collection is blocked, but it also means the event is not sent.
Storage and domains
Default visitor identity is derived on the server per website and UTC day without persistent browser measurement storage. Only opt-in cross-domain handoff uses same-day sessionStorage. The browser opt-out preference can remain in localStorage. The configured domain and its subdomains share identity; unrelated domains need the explicit handoff. Use separate website tokens for separate reports.
See Subdomains and separate websites for shared-token reporting, hostname exclusions and verification on each host.
First-party collect
Settings → Installation → Dashboard → First-party collect gives the website a dedicated hostname such as yoursite-collect.seean.io. Enable first-party collect assigns it instantly; there is nothing to change in your own DNS. The Scripts tab then generates the snippet with that hostname for both the script and data-api, so the page no longer names app.seean.io. Paste the new snippet; pages that still use the old one keep working. Disable releases the hostname and the snippet returns to app.seean.io. A failed Installation action shows the server’s reason; a rate-limited one says how many seconds to wait.
Whenever the hostname used by the generated snippet changes, Seean clears the earlier installation verification and starts automatic checks again at the first-day pace. This applies to first-party enable/disable and custom-domain activation, loss or disconnection. It does not remove analytics data or treat a check of the old address as proof that the new one is installed. Publish the current snippet and update your security-policy origins, then use Setup health → Check installation (verification details). Saving settings without changing the effective hostname keeps the existing verification.
The hostname is still under seean.io and requests still reach Seean’s collector, so this is not a custom domain on your own site and offers no privacy change for visitors. It reduces matches against generic blocklists that name app.seean.io, and it keeps each website’s collection address separate. The Content Security Policy origins below change to the dedicated hostname.
Custom collection domain
Your own domain (below First-party collect) lets the script and the collector answer on a hostname you own, such as analytics.yourdomain.com. It requires the Seean collect subdomain above to be enabled, because your DNS record points at it:
- In your DNS, add a CNAME for the hostname you chose pointing at your website’s
<slug>-collect.seean.ioname (shown in the row). Do not use the apex domain; a subdomain is required. If your DNS is on Cloudflare, keep the record DNS only (grey cloud): a proxied record hides the CNAME and cannot be verified. A CNAME chain that ends at the collect name is accepted. - Enter the hostname and choose Connect. Seean looks the CNAME up over public DNS; a missing record or one pointing anywhere else (including another website’s collect name) is refused - that record is the proof that you control the hostname for this website.
- The row shows pending while the certificate for your hostname is validated over HTTP and issued; Seean rechecks every ten minutes for two hours, and Retry starts a fresh round of checks immediately. The domain becomes active only when the certificate is live and
https://<your host>/k.jsserves the tracker. - Once active, the Scripts tab and the installation banner generate the snippet with your hostname for both
srcanddata-api; paste it again. Pages that still use the Seean hostname keep working.
Verification failed shows the certificate service’s reason (for example an HTTP validation failure or a hostname that is already registered elsewhere) - fix the DNS record and choose Retry. Disconnect removes the certificate registration and the snippet returns to the Seean hostname; pages using your domain then stop sending data until you paste the new snippet. Deleting the website also releases the domain so it can be connected from another website; disabling the Seean collect subdomain later does not disconnect an active domain (its DNS record keeps resolving), so choose Disconnect first if you no longer want it. Only the tracker (/k.js) and the collector (/api/collect, /api/collect-config) answer on your domain; every other path returns 404. Your domain is served through Cloudflare, so visitors still connect to Cloudflare’s edge and Seean’s collector, only under your name. Connect, Retry and Disconnect are limited to six actions a minute per account, and the demo website cannot use a custom domain. On deployments without the certificate service the row says the feature is not available.
Content Security Policy
If your website sends a Content Security Policy, allow the generated script origin in script-src and the generated collection origin in connect-src. For the standard production snippet, the additions are:
| Existing directive | Origin to allow |
|---|---|
script-src (and script-src-elem if defined) |
https://app.seean.io, or your first-party collect hostname |
connect-src |
https://app.seean.io, or your first-party collect hostname |
Merge these origins into your existing directives. Preserve the sources, nonces or hashes your application already needs. Do not add unsafe-inline or unsafe-eval just for the external tracker. Your own inline event handlers may have separate policy requirements. Check the browser console for blocked script or collection requests after publishing. Settings → Installation → Setup health → Check installation can identify a blocking HTTP script/connection policy on your homepage and show the needed sources; see diagnostic results and limits.
Review exclusions and technical privacy before installing. Use feature availability for unsupported tracking modes. Removing the script stops collection on newly loaded documents; already open pages retain their loaded code until closed or reloaded.
Something unclear? Contact support.