Tracker — spec

Table of Contents

1. Purpose

The tracker is the passive-capture layer of the wal.sh adtech surface. One browser module (wal-sh.site.tracker.browser) installs a fixed set of document-level listeners; each dispatches into pure logic in wal-sh.site.tracker.core. No per-page instrumentation, no data-layer push calls at the render site — every event derives from a native DOM event plus data-track-* attributes on the element.

Existence rationale is research, not analytics. The tracker is the control surface for the attribution-audit ledger, the intent-signals accumulator, and the stuffing-detectors suite — those three modules read events the tracker emits. The tracker also matches the shape of the industry passive-capture profile studied in the million-site measurement (Englehardt and Narayanan 2016), so wal.sh's event vocabulary maps onto measurement-paper terminology one-to-one.

ORACLE reference implementation: src/wal_sh/site/tracker/core.cljc (pure logic) + src/wal_sh/site/tracker/browser.cljs (DOM adapter). Envelope reuse: wal-sh.adtech.beacon.core/build — the tracker and the explicit-beacon module produce identical JSON.

2. Requirements

T1
One document-level listener per event class. No per-element handlers; the tracker never touches DOM at mount time except to install the observers.
T2
Event names come from the closed set in wal-sh.site.tracker.core/known-events; adding a new event name requires updating the set.
T3
Every event carries the shared beacon envelope. Element identity is a sub-map built by element-payload from a plain map, not a live DOM node — keeps core host-neutral.
T4
Transport is a pixel GET (Image().src) to a same-origin or beacon-host endpoint; piggybacks on img-src, not connect-src. See 5.
T5
Bot UA + navigator.webdriver = drop the event before serialization.
T6
?tracking=off URL param disables the tracker for the page.
T7
Total URL length after query-encoding is capped at max-query-len (1800 bytes); over-cap events are truncated before send.
T8
Deterministic sampling — the FNV-1a of the session id mod 100 decides all-in or all-out for the page-life. No half-populated funnels.
T9
sessionId is opaque, rotating per page-life; no cross-session cookie.
T10
The tracker exposes window.__wal_tracker with .send, .rescan, .session — nothing else.
T11
Impression fires only when the element is ≥50 % in-view for ≥1 s (impression-dwell-ms). Below this it is not an impression.
T12
Hover fires only when the pointer dwells for ≥500 ms (hover-dwell-ms).
T13
Reverse scroll fires once per session, after ≥50 % depth reached, on an upward move ≥200 px.
T14
Sponsored events are distinct names (sponsored.impression, sponsored.click) from organic (element.*); both carry elKind so downstream can join them.

3. Contract signature

Public surface (browser, string-keyed for :advanced-safe interop):

window.__wal_tracker = {
  send:    (fn [event-name extra] -> nil)      ; fire an event with merged extras
  rescan:  (fn []                 -> nil)      ; re-run IntersectionObserver over new DOM
  session: (fn []                 -> string)   ; opaque per-page-life id
}

Core (host-neutral, :clj + :cljs):

(scroll-pct scroll-y viewport-h doc-h)            ; -> int in [0,100]
(crossed-milestones pct fired-set)                ; -> set of newly-crossed
(element-payload attrs-map)                       ; -> sub-map for envelope
(sample? session-id pct)                          ; -> bool, deterministic
(valid-event? name)                               ; -> bool
(clip s)                                          ; -> s truncated to max-attr-len
(sanitize-el-attrs m)                             ; -> m with every string clipped

4. Event catalogue

Event Trigger Sampling
pageview init 100 %
page.dwell visibilitychange=hidden or pagehide 100 %
scroll.depth 25 / 50 / 75 / 100 % milestones 100 % (4 max)
scroll.reverse ≥200 px up after ≥50 % depth first-per-session
element.impression IntersectionObserver ≥50 % ≥1 s 100 %
element.dwell element leaves viewport after impression 100 %
element.click pointerup on <a> / <button> / [data-track-id] 100 %
element.hover pointerover ≥500 ms 100 %
sponsored.impression as element.impression, restricted to data-track-kind=sponsored-* 100 %
sponsored.click as element.click, restricted 100 %
form.submit form submit 100 %
form.focus focusin on input/textarea/select 100 %

5. Transport (v6)

Was: POST beacon.termbox.org/webhook via sendBeacon / fetch keepalive. Blocked by connect-src 'self' in the deployed CSP.

Now: Image().src GET to beacon.termbox.org/pixel; payload encoded as query params, response is a 1×1 GIF (MDN Web Docs, n.d.-a). See the OpenAPI spec at docs/beacon-termbox-spec.json. Same-origin fallback /static/t.gif for hosts where img-src does not permit the beacon host. Access logs on the pixel host become the analytics stream.

The pixel technique predates and outlasts sendBeacon (MDN Web Docs, n.d.-c); Roesner et al. classify the same shape as "vanilla" third-party tracking (Roesner, Kohno, and Wetherall 2012).

tracker-transport.png

6. Sampling + gating

  • sample? is FNV-1a of sessionId mod 100 vs a per-class percentage. All-in or all-out for the page-life; funnels stay monotone.
  • Bot UAs and navigator.webdriver drop the event at send time before URL construction. Crawler traffic remains uncounted; see Ahmad et al. on the reverse problem — measurements taken via crawlers over-count what the browser actually renders (Ahmad et al. 2020), (Jueckstock et al. 2021).
  • ?tracking=off disables per-page. Documented in the site's own privacy note.
  • max-query-len (1800) is the practical CDN truncation floor; the spec-legal URL length is higher but many intermediaries truncate.

7. Related literature

  • Roesner, Kohno & Wetherall define the passive-capture profile wal.sh matches: single script, document-level listeners, GET pixel (Roesner, Kohno, and Wetherall 2012).
  • Englehardt & Narayanan's million-site OpenWPM crawl is the reference measurement for this event vocabulary; the known-events set maps onto their observed handlers (Englehardt and Narayanan 2016).
  • Acar et al. on persistent identifiers explains why wal.sh's tracker refuses to write a cross-session cookie: any stable identifier is a fingerprint substrate (Acar et al. 2014).
  • IntersectionObserver semantics: MDN (MDN Web Docs, n.d.-b).
  • sendBeacon was the intended transport before CSP forced the switch; MDN documents the keepalive fallback pattern (MDN Web Docs, n.d.-c).
  • Image() constructor as pixel transport: MDN (MDN Web Docs, n.d.-a).
  • Sec-Fetch-Site is the browser-side signal that maps onto our sponsored? predicate at the origin level (W3C 2021).
  • rel=sponsored is the outbound-link analogue of our sponsored event vocabulary (WHATWG, n.d.).

8. Cross-references

  • beacon/spec.org — envelope + serialization contract the tracker reuses verbatim.
  • attribution-audit/spec.org — reads sponsored.* + element.click events into the provenance ledger.
  • intent-signals/spec.org — reads page.dwell, scroll.depth, element.click into the score accumulator.
  • exit-intent/spec.org — reads scroll.reverse + intent-signals eligibility.
  • pocket-es-integration/spec.org — calls rescan after search results mount so newly-inserted DOM is observed.
  • ORACLE:
    • src/wal_sh/site/tracker/core.cljc (pure)
    • src/wal_sh/site/tracker/browser.cljs (DOM adapter)
  • [BROKEN LINK: No match for fuzzy expression: *2013–2018: Mobile + programmatic era] on how event vocabularies standardised via IAB frameworks.
  • Web Trackers Analysis (Alexa Top 800) — 2012 Graphviz map of third-party tracker ecosystem (DoubleClick / Comscore / Quantcast / BlueKai); the passive-capture surface this tracker inverts.
  • SLIDR: Real-Time Robotic Traffic Detection — the invalid-traffic detection layer that consumes the raw event stream this tracker emits.

9. Open questions

  • Batching. Current tracker fires one pixel per event; a 1 s debounce with flush on visibility-change would cut request count 10-40x at the cost of losing the last N events on a hard tab-kill. The pixel-transport switch (v6) made this less urgent — one GIF fetch is cheap — but not moot. Design decision: preserve tail-latency events or preserve request count.
  • Sampling percentage per event class. Everything is currently 100 % — the site's traffic is low enough that sampling is not a cost driver. If the site scales, sample? is already deterministic- per-session and lets us drop entire classes without funnel damage.
Acar, Gunes, Christian Eubank, Steven Englehardt, Marc Juarez, Arvind Narayanan, and Claudia Diaz. 2014. “The Web Never Forgets: Persistent Tracking Mechanisms in the Wild.” In Proceedings of the 2014 Acm Sigsac Conference on Computer and Communications Security (Ccs ’14), 674–89. https://doi.org/10.1145/2660267.2660347.
Ahmad, Syed Suleman, Muhammad Daniyal Dar, Muhammad Fareed Zaffar, Narseo Vallina-Rodriguez, and Rishab Nithyanand. 2020. “Apophanies or Epiphanies? How Crawlers Impact Our Understanding of the Web.” In Proceedings of the Web Conference 2020 (Www ’20), 271–80. https://doi.org/10.1145/3366423.3380113.
Englehardt, Steven, and Arvind Narayanan. 2016. “Online Tracking: A 1-Million-Site Measurement and Analysis.” In Proceedings of the 2016 Acm Sigsac Conference on Computer and Communications Security (Ccs ’16), 1388–1401. https://doi.org/10.1145/2976749.2978313.
Jueckstock, Jordan, Peter Snyder, Shaown Sarker, Alexandros Kapravelos, and Benjamin Livshits. 2021. “Towards Realistic and Reproducibleweb Crawl Measurements.” In Proceedings of the Web Conference 2021 (Www ’21), 80–91. https://doi.org/10.1145/3442381.3450050.
MDN Web Docs. n.d.-a. “Image() Constructor.” https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/Image.
———. n.d.-b. “Intersection Observer Api.” https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API.
———. n.d.-c. “Navigator: Sendbeacon() Method.” https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon.
Roesner, Franziska, Tadayoshi Kohno, and David Wetherall. 2012. “Detecting and Defending against Third-Party Tracking on the Web.” In 9Th Usenix Symposium on Networked Systems Design and Implementation (Nsdi 12), 155–68. https://www.usenix.org/conference/nsdi12/technical-sessions/presentation/roesner.
W3C. 2021. “Fetch Metadata Request Headers.” https://www.w3.org/TR/fetch-metadata/.
WHATWG. n.d. “Html Living Standard: Link Types (Rel=Sponsored).” https://html.spec.whatwg.org/multipage/links.html#link-type-sponsored.