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>Attributes
Section titled “Attributes”| 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.
Web vitals
Section titled “Web vitals”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.
data-exclude glob syntax
Section titled “data-exclude glob syntax”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.
Client-side suppression
Section titled “Client-side suppression”Before anything is sent, the tracker drops the hit entirely when any of these is true:
localStorage.tracing_disabled === "1"— the visitor opted out.- The host is
localhost,127.0.0.1or[::1], or the protocol isfile:— unlesslocalStorage.tracing_debug === "1". location.pathnamematches adata-excludeglob.
See Opt-out & debugging.
Identity mode
Section titled “Identity mode”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.
What a hit looks like
Section titled “What a hit looks like”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.
{ "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 |
Collector endpoints
Section titled “Collector endpoints”| 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.
Limits and rejections
Section titled “Limits and rejections”| 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.