Skip to main content

What this lets you do

Record custom conversion events (signups, purchases, demo bookings) with revenue amounts, link visitors to your own user IDs, and control tracking from your own consent banner, using the window.traceten global the snippet installs.

Before you start

Script tag attributes

The snippet reads exactly two attributes off its own <script> tag. Both are rendered for you on the install page. Anything else you add to the tag is ignored. The data-traceten attributes further down this page go on your own page elements, not on the script tag.

The window.traceten global

The snippet writes exactly one global: window.traceten. It is a callable function with five attached methods: Every method is wrapped so it never throws into your page. Invalid input is silently dropped, not rejected with an error. The troubleshooting section below lists the reasons an event can be dropped.

Calling before the snippet loads

The snippet loads with async, so code that runs early may execute before window.traceten exists. Install this stub before the snippet tag and calls made in the gap are queued and replayed once the snippet initializes:
Queued identify calls use the shape ["identify", arg]; anything else in the queue is replayed as a track call.

Pageviews in single-page apps

The snippet records a pageview once, when it first loads. It does not automatically detect client-side route changes. After each navigation in a single-page app, call the function form:
This closes the measurement window for the page you are leaving and reports it, then starts a fresh window for the new page (scroll depth, time on page, active time, exit signals) and sends a new pageview event. Both events go out in a single request, so per-page metrics stay accurate across SPA navigations without doubling the number of requests you send. Framework examples are in the Next.js guide and troubleshooting.

traceten.track(eventName, properties?)

Records a custom conversion event. Use it for the moments that matter to revenue: completed signups, purchases, booked demos, started trials.

Event name rules

The event name must match the pattern ^[a-z][a-z0-9_]*$: Valid: signup, demo_booked, trial_started_v2. Invalid: Signup, demo-booked, 2fa_enabled, _internal.

Property rules

properties is an optional flat object. The snippet sanitizes it before sending: The reserved keys value_cents and currency (below) count toward the 10-key limit.

Recording revenue: value_cents and currency

Two property keys are reserved. value_cents (a number, in the smallest currency unit) and currency (a string) are lifted out of properties and sent as top-level fields, which is how the dashboard attributes revenue to AI traffic:
  • value_cents must be a number. It is rounded to the nearest integer. 4900 means $49.00 in USD.
  • currency must be a string, for example "usd" or "eur". Case does not matter: the code is uppercased at ingestion and stored as USD. See Currencies for the list Traceten can attribute revenue in.
  • If either has the wrong type it is treated as a regular property and does not attribute revenue. currency must also be exactly 3 characters: the ingestion endpoint rejects any other length, so a value like "dollars" stays an ordinary property rather than being promoted and losing the whole event.

When a track call is dropped

A track call sends nothing when any of the following is true. Note that an explicit track() call is treated as more intentional than a declarative goal: it is kept while a recognised consent tool is still deciding, whereas the HTML triggers are not installed until that decision arrives.
  • The visitor denied consent, or your site uses consentDefault: "pending" and consent is still unresolved (see the consent guide).
  • The visitor has Do Not Track enabled or the _traceten_optout cookie set.
  • The event name fails validation.
Consent behavior while a recognised CMP (TCF, OneTrust, Cookiebot) is still deciding: up to 10 calls are buffered in memory (no cookie is written and nothing is sent yet) and replayed once consent resolves to granted or no CMP is detected. Calls beyond the 10th are dropped silently. This is the same buffering identify() uses below, and it is why “kept” above means “not dropped,” not “sent immediately.”

traceten.identify(arg)

Links the current visitor to your own user ID or email, so conversions that happen later (for example, a Stripe subscription) can be attributed back to the AI source that first brought the visitor. Three call shapes:
Validation: Fields that fail validation are dropped. If neither field survives, the call is a no-op. What happens on a successful call:
  1. The snippet POSTs to https://ingest.traceten.com/v1/identify. The email never enters Traceten analytics storage in plaintext; the server returns a one-way hash.
  2. The snippet rewrites the _traceten_vid visitor cookie to that server-computed hash, so future sessions by the same identified user connect to the same visitor record.
Consent behavior: if consent is denied, the call is dropped. If consent is still pending, up to 10 identify calls are buffered in memory and sent once consent is granted.

traceten.resolveConsent(state)

For sites that run their own consent banner instead of a supported CMP. Call it when the user makes a choice:
  • Only "granted" and "denied" are accepted. Any other value is a no-op.
  • "denied" while consent is pending clears all queued events and buffered identify() and track() calls, and removes tracking cookies.
  • "granted" after an earlier denial starts a fresh consented session.
To hold all tracking (including pageviews) until the user answers your banner, set consentDefault: "pending" in a pre-load stub. The full flow is in the consent guide.

Declarative click tracking: data-traceten

To track clicks on a specific element without writing JavaScript, add a data-traceten attribute with a label:
Each click sends a click event carrying the label, the element’s id (if any), and the link URL. Behavior details:
  • Clicks on children of the labeled element count. The snippet walks up to 6 ancestor levels to find the nearest data-traceten attribute.
  • Labels are clamped to 64 characters and PII-redacted like property values.
  • Repeat clicks on the same element within 1 second are deduplicated.
  • The link URL is included only for same-origin links, with sensitive query parameters stripped.
  • Consent and opt-out are checked on every click; blocked visitors send nothing.

Declarative goals: data-traceten-goal and data-traceten-scroll

Two more attributes fire a goal, which is a custom conversion event under the name the dashboard counts it by.
Both send through the same endpoint as traceten.track(), with the same opt-out gate and the same property sanitizer.
  • Goal names must match ^[a-z][a-z0-9_]*$ and may not be one of the reserved billing names. An invalid or reserved name is a silent no-op.
  • data-traceten-goal-* attributes become properties, kebab-case converted to snake_case, on either trigger. Both the key and the value are stored and read back: see what happens to property keys and values.
  • Click goals reuse the data-traceten listener: children count, and repeat clicks within 1 second are deduplicated.
  • Scroll goals fire at most once per element, per page load, not once per URL. A route change arms whatever has not fired yet, including the sections the new route just rendered, so a client-side navigation and a fresh load of the same URL record the same thing. They need IntersectionObserver, and do nothing where it is absent.
They do not share track()’s behaviour while consent is unresolved. Both listeners are installed only once a consent decision has been made, so a click or a scroll before your CMP answers records nothing, while a traceten.track() call in that same window is kept. The window is short but it covers the moment a visitor lands and clicks a hero button. If a goal has to survive it, call traceten.track() from your own handler. data-traceten and data-traceten-goal are independent. An element may carry both, and each fires its own event, in either nesting order. The two goal triggers have different lifetimes across a back/forward restore. The click listener is removed when the page is hidden (pagehide), which is long-standing behaviour of data-traceten click tracking that goal clicks inherit; the scroll observer is not. So on a page restored from the browser’s back/forward cache, data-traceten-scroll still fires and data-traceten-goal no longer does, until the next full load. If a click goal has to survive that, call traceten.track() from your own handler. Full detail: Goals and scroll tracking. Without this attribute, Traceten’s identity cookies are host-only: a cookie set on www.example.com is not sent to checkout.example.com. A visitor who lands on one subdomain and converts on another presents no cookie on the second host, is counted as brand new, and their conversion is never attributed to the visit that produced it. This is off by default; creating a site does not turn it on. The dashboard does compute a suggestion (your registrable domain, derived from the domain you entered) and pre-fills it into Sites → Settings → Cookies, unconfirmed. Once you confirm it there, the value renders on every snippet the install page hands out:
_traceten_vid and _traceten_sid are then written with Domain=example.com and shared across every subdomain, so one visitor stays one visitor from landing page to checkout. Behavior details:
  • Use the apex only: example.com, not .example.com or www.example.com. A leading dot is stripped.
  • The value is ignored unless the current host is the apex itself or a subdomain of it. A mismatch falls back to host-only cookies rather than failing, so a staging host on a different domain degrades safely.
  • If the browser rejects the broadened cookie, the snippet retries host-only so tracking still works on that page.
  • Set it on every page that loads the snippet. A page that omits it writes a host-only cookie that the other subdomains cannot read.
  • Only identity and cart cookies are broadened. The opt-out cookie is broadened too, deliberately, so opting out on one subdomain opts the visitor out across all of them.
  • Some domains have no apex a browser will accept a Domain for, such as localhost or a bare IP. The attribute is omitted for those and cookies stay host-only.
To change the value or remove it, open Sites → Settings → Cookies. Clearing it scopes cookies to each exact host. Because the cookies are readable on every subdomain under the apex, clear it if you do not control every subdomain under your domain.
This only helps across subdomains of one registrable domain. It cannot span genuinely different domains: a browser will never send an example.com cookie to example-checkout.com. For hosted checkouts see below.

Hosted checkouts on another domain

Cookies cannot cross to a different registrable domain, so Traceten bridges the two most common cases without configuration: For any other checkout on a different domain, call traceten.identify() once the visitor authenticates so the pre-login visit and the post-login conversion resolve to one person.

Endpoints the snippet calls

For CSP allowlists and network debugging, these are the requests a correctly installed snippet makes: Events and conversions are sent with navigator.sendBeacon, falling back to fetch with keepalive: true, so they survive page unloads. Your Content-Security-Policy needs connect-src https://ingest.traceten.com (see CSP troubleshooting).

Verify it worked

  1. Open DevTools, go to the Network tab, and filter by ingest.traceten.com.
  2. Trigger your event (for example, run traceten.track("test_event") in the console).
  3. You should see a POST to /v1/conversions. Inspect the request payload to confirm event_name and properties are what you expect.
  4. The event appears in the dashboard under Events within a minute.

Troubleshooting

No request appears when I call traceten.track(). Check the event name against the rules above (lowercase start, no hyphens, no capitals). Then check for an ad blocker, Do Not Track, or a denied consent state. All of these drop the event silently by design. value_cents shows up inside properties instead of attributing revenue. It was not a number (for example, you passed "4900" as a string). Pass a numeric value in cents. A property is missing from the received event. You passed more than 10 keys, or the key is longer than 40 characters. Both are dropped silently. A property value shows [redacted]. The value matched a PII pattern (email, phone number, card number). This is intentional: do not send PII in event properties. Use traceten.identify() for emails. identify did not change anything. The id exceeded 128 characters, or the email had no @ or exceeded 254 characters, or consent is denied. If consent is pending, the call is buffered and sent after the user grants consent.

Next