Beacon — spec
Table of Contents
1. Purpose
Beacon is the shared envelope. Two callers — the passive
tracker and the explicit-page-view module
window._beacon.pageView() — build the same JSON via
wal-sh.adtech.beacon.core/build. The downstream server sees one
schema; the browser sees two callers.
The envelope predates the tracker; it was designed for
navigator.sendBeacon to a sinkhole and later reused for the pixel
transport (MDN Web Docs, n.d.-b), (MDN Web Docs, n.d.-a). Keeping a single
builder + a single JSON schema is the contract: any module fires
events, downstream cannot tell them apart at the payload level.
ORACLE: src/wal_sh/adtech/beacon/core.cljc. Endpoint OpenAPI:
docs/beacon-termbox-spec.json.
2. Requirements
- B1
- One builder —
beacon.core/build— produces(payload, json, oversize?)for both callers. No parallel path. - B2
- Payload keys are a fixed base set (
event,ts,session,url,referrer,userAgent,screen,timezone,pageID) plus a caller-supplied:extramerged last. - B3
- An empty or blank
event-namereturnsnilfrombuild— the browser adapter short-circuits without firing. - B4
payload->jsonhand-rolls JSON socore.cljcstays dependency-free. Values are strings or numbers only; booleans are emitted as baretrue=/=false. Escape sequences are quote, backslash, and control chars.- B5
oversize?is the byte length of the UTF-8 JSON compared againstmax-payload-bytes(65,536, thesendBeaconcap). The browser adapter may still choose to send oversized events viafetch keepalive— the flag advertises the concern, not the response.- B6
- Session id is caller-supplied;
gen-session-idtakes a numeric seed and producess-<base36>(JVM:Long/toString; JS:.toString(36)on absolute value). - B7
- Endpoint URL is a literal in
core(beacon.termbox.org/webhookhistorically; the tracker's pixel path is a separate constant intracker.core). Downstream retargeting is a constant swap, not a code change.
3. Contract signature
Pure (core.cljc):
(gen-session-id seed) ; -> "s-<base36>"
(valid-event? event-name) ; -> bool
(payload event-name ctx) ; -> map or nil
(payload->json m) ; -> string or nil
(oversize? json-str) ; -> bool
(build event-name ctx)
;; -> {:payload <map> :json <string> :oversize? <bool>} or nil
Browser surface:
window._beacon = {
pageView: (fn [] -> nil) ; fires "pageview"
send: (fn [event extra] -> nil) ; low-level, sendBeacon
session: (fn [] -> string) ; opaque session id
}
Endpoint contract (docs/beacon-termbox-spec.json, abridged):
POST /webhook— JSON body ≤ 64 KiB; 204 No Content on success.GET /pixel?<params>— returns a 1×1 transparent GIF, logs all params. Used by the tracker under CSP.GET /spec.json— OpenAPI 3.0 self-description.
4. Payload shape (verbatim)
{
"event": "sponsored.impression",
"ts": 1697000000000,
"session": "s-abc123",
"url": "https://wal.sh/search?q=dafny",
"referrer": "",
"userAgent": "...",
"screen": "1920x1080",
"timezone": "America/New_York",
"pageID": "",
"elId": "sponsored-sr-001",
"elKind": "sponsored-product",
"creativeId":"sr-001",
"unit": "product",
"position": 3
}
Extras (elId through position above, plus sponsor, pageIdx,
poolIdx, isDup, dwellMs, depthPct, …) come from the tracker's
element-payload merge before query-encoding.
5. Related literature
sendBeaconspecification and itskeepalivecounterpart: MDN (MDN Web Docs, n.d.-b).sendBeaconis the intent-preserving choice for events fired at page-unload; the envelope was sized around its 64 KiB cap.Image()constructor pixel transport, still the industry-standard fallback: MDN (MDN Web Docs, n.d.-a).- The tracking-envelope shape (event + session + URL + user-agent extras) matches the profile documented for third-party trackers in Roesner et al.'s NSDI 2012 measurement (Roesner, Kohno, and Wetherall 2012) and Englehardt & Narayanan's CCS 2016 million-site crawl (Englehardt and Narayanan 2016).
- CEDDL (
pageID) is the customer-experience data-layer standard thepageIDfield is named after; also spelledpageInfo.pageIDin the W3C CEDDL 1.0 spec. Modelled but not fully wired. - Sec-Fetch-* headers give the origin-side signal that would let a server-side pipeline distinguish beacon from real navigation (W3C 2021); the current sinkhole doesn't use them.
- OpenRTB defines a comparable envelope for bid-request payloads; our envelope is smaller and always outbound (IAB Tech Lab 2016).
6. Cross-references
- tracker/spec.org — primary caller; reuses
buildverbatim for every event. - attribution-audit/spec.org — the ledger appends touchpoint
records that are shaped like beacon extras (
source,medium,campaign). - ORACLE:
src/wal_sh/adtech/beacon/core.cljc(pure)docs/beacon-termbox-spec.json(endpoint OpenAPI)
- [BROKEN LINK: No match for fuzzy expression: *2007–2013: Programmatic exchange era] for the RTB envelope ancestry.
- The Four-Boundary Spec Mapping — OpenAPI as the message-boundary spec technology this envelope sits in.
- Web Trackers Analysis (Alexa Top 800) — the 2012 pixel-transport ecosystem the beacon module inherits from.
7. Open questions
- Retry semantics. Neither the pixel nor the
sendBeaconcall retries on network failure; the browser doesn't guarantee delivery. For research the loss rate is acceptable; a production system would add a per-session write-ahead buffer inlocalStorageflushed on next pageview. The trade-off is that persistence turns the tracker into a first-party persistent identifier — precisely the pattern Acar et al. document as evercookie substrate (Acar et al. 2014). - Response echoing. The pixel is a 1×1 GIF with no envelope response.
An
X-Server-Timeheader would let us measure clock skew per session but adds latency.