Skip to content

Script configuration

The tracker is configured entirely through attributes on its own <script> tag. There is no init call and no configuration object.

<script defer
data-site="pk_live_xxxxxxxx"
data-api="https://ingest.tracing.tools"
data-hash="false"
data-outbound="true"
data-auto="true"
data-exclude="/admin/**,/preview/*"
src="https://ingest.tracing.tools/t.js"></script>
Attribute Default Effect
data-site Required. Your site’s public key, pk_live_…. Without it the tracker returns immediately and does nothing.
data-api https://ingest.tracing.tools Collector origin. A trailing slash is stripped. Hits go to <data-api>/e, the identity probe to <data-api>/c/<key>.
data-hash false Set to "true" to treat #hash changes as pageviews and to include the hash in the path comparison.
data-outbound true Set to exactly "false" to stop recording clicks on links to other hosts.
data-auto true Set to exactly "false" to suppress the automatic first pageview. You then call tracing.pageview() yourself.
data-exclude empty Comma-separated path globs. A page whose location.pathname matches any of them sends nothing at all.
data-vitals true Set to exactly "false" to stop reporting Core Web Vitals. See Web vitals.
data-cookies false Set to "true" to skip the identity probe and assume cookie mode from the first hit. Only useful on a site the collector has already been switched to cookie mode; see below.

Booleans are compared as strings. data-outbound="0" does not disable outbound tracking — only the literal "false" does. Likewise only "true" enables data-hash and data-cookies.

On every real page load the tracker measures LCP, INP, CLS, FCP and TTFB with the browser’s own PerformanceObserver and sends one report when the page is first hidden or closed. Reports are not events and never count toward your plan. Set data-vitals="false" to switch it off. What is measured, and how it is read, is on Performance.

Patterns are anchored at both ends and matched against location.pathname (never the query string or the hash).

Pattern Matches Does not match
/admin /admin /admin/users
/admin/* /admin/users /admin/users/7, /admin
/admin/** /admin/users, /admin/users/7 /admin
/preview/* /preview/abc /preview

* matches anything except /. ** matches across segments.

Before anything is sent, the tracker drops the hit entirely when any of these is true:

  1. localStorage.tracing_disabled === "1" — the visitor opted out.
  2. The host is localhost, 127.0.0.1 or [::1], or the protocol is file:unless localStorage.tracing_debug === "1".
  3. location.pathname matches a data-exclude glob.

See Opt-out & debugging.

Before the first hit, the tracker asks the collector which identity mode the site is in:

GET https://ingest.tracing.tools/c/pk_live_xxxxxxxx
{ "m": "hash" }

Everything the page queued is held until that answer arrives, or for 1500 ms, whichever comes first. Holding matters: the landing hit is the one that carries the campaign, and sending it under a provisional identity would file the landing page and the checkout under two different visitors.

Every response to POST /e repeats the same { "m": … }, so a mode changed in the dashboard takes effect on the next hit from every browser without anyone editing a snippet.

hash is the mode every site created through the dashboard is in, and it is the cookieless one. data-cookies="true" only skips the wait; it cannot make the collector accept a browser-supplied id, because the collector ignores the id field unless the site row itself says cookie.

Hits are POSTed as JSON with content-type: text/plain (which keeps the request a CORS simple request and avoids a preflight). On page hide the tracker uses navigator.sendBeacon instead.

POST /e
{
"s": "pk_live_xxxxxxxx",
"t": "pageview",
"u": "https://acme.com/pricing?utm_source=hn",
"r": "https://news.ycombinator.com/",
"ti": "Pricing — Acme",
"l": "en-GB",
"w": 1512,
"h": 842,
"d": 8231
}
Field Meaning
s Site public key
t pageview, custom, outbound, revenue, identify or duration
u location.href
r document.referrer
ti document.title
l navigator.language
w / h window.innerWidth / innerHeight
n Event name; for outbound it is the full destination URL
d Milliseconds — time on the previous page, or the flushed duration
p Properties (custom events) or traits (identify)
v Revenue in the currency’s minor unit
c Currency code
i First-party visitor id — only present, and only honoured, in cookie mode
b Automation signals read off the browser once at load, as a bitmask. See Bots & exclusions
x 1 once the page has been moved over, tapped, typed in or scrolled. Absent until then
Method Path Purpose
GET /t.js The tracker bundle. cache-control: public, max-age=3600, stale-while-revalidate=86400, with an ETag.
GET /c/:key Identity-mode probe. cache-control: public, max-age=60. 404 for an unknown key.
POST /e Collection.
POST /api/send Identical alias, so an existing umami integration can be repointed.
POST /crawl Server-side crawler reporting, for the bots that never run JavaScript. See Bots & exclusions.
GET /health {"ok":true,"service":"ingest"}

CORS is open (origin: *, methods POST, GET, OPTIONS, header content-type, max-age: 86400), because the collector must accept hits from any site that holds a valid key.

Rule Value Response
Request body 32 KiB 413 payload too large
URL first 2000 characters parsed 400 bad url if unparseable or host-less
Missing s or u 400 bad payload
Unparseable JSON 400 bad payload
Unknown site key 404 {"error":"unknown site"}
Excluded IP or excluded path 202 with an empty body, nothing written
A named crawler, or a client scored as automation 202 with an empty body; recorded on Crawlers, never as an event
Accepted 202 {"m":"hash"}

Truncation applied before writing: event name and title 500 characters, referrer 2000, language 35, property key 200, string property value 500, at most 50 properties or traits per hit. A flushed duration is clamped to 0–30 minutes.