Docs

Ingest protocol

The browser SDK speaks this protocol for you. You only need it to write your own client, to build a proxy, or to understand what you see in the network tab.

Endpoints

EndpointPurpose
POST /v1/e (alias /v1/collect)Tracking batch, text/plain JSON (no CORS preflight). Always bodyless: 204 stored or deliberately ignored, 400 malformed, 403 unknown key or origin not allowed, 413 too large, 429 rate limited
OPTIONS /v1/eCORS answer for allowed origins (robustness for fetch fallbacks)
GET /v1/c/{pk}Client config of the publishable key's project (cached 60 s, public)
GET /v1/s/{file}SDK scripts: x3.js, x3.full.js, plugins x3.video.js, x3.sections.js, x3.forms.js, x3.identify.js, x3.consent.js, x3.bookings.js, versioned x3@1.2.3.js. ?k=pk_… inlines the client config
POST /v1/cbConversion beacon for a conversion definition (≤ 4 KB, 4/min/IP, once per session and definition)
POST /v1/projects/{project}/eventsServer-side events with a secret key – see Server helper

Batch format (wire v2)

{
  "v": 2,
  "k": "pk_3x_…",
  "sid": "3f0c…",
  "fresh": true,
  "consent": false,
  "test": false,
  "pvs": [{
    "id": "b2d1…", "seq": 3, "age": 41250, "nav": "navigate",
    "host": "www.example.com", "path": "/pricing", "variant": null, "lang": "en",
    "ref": "www.google.com", "attrFrom": "url",
    "attr": { "utm_source": "linkedin", "utm_medium": "paid_social", "utm_campaign": "q4", "li_fat_id": "…" },
    "vw": 1280, "vh": 720, "sw": 1280, "tp": false,
    "ph": 5400, "sy": 2100, "sp": 52, "marks": { … },
    "sec": { "hero": [0, 8200], "pricing": [9400, 12000] },
    "eng": 30500, "clk": 4, "iclk": 2, "vids": []
  }],
  "ev": [{ "pv": "b2d1…", "n": 0, "t": 12000, "e": "cta_click", "p": { "cta": "book-demo" } }],
  "cv": 7,
  "lib": "x3-js@1.0.0"
}
  • Pageviews are cumulative. Each send carries the complete current state with a higher seq; the server applies it only when seq is newer. Lost or duplicated sends therefore never corrupt data.
  • age is milliseconds since the pageview started; the server computes the start time from its own clock, so wrong client clocks do not matter.
  • sid is a random id kept in memory (or in sessionStorage after consent). fresh: true tells the server that the id was created on this document load, so it may continue the visitor's session by the daily salted hash instead – see Cookieless sessions.
  • attr holds up to 13 attribution keys (six UTMs, seven click ids). Click ids are kept complete for conversion uploads.
  • Events (ev) reference a pageview of the same batch; pv + n is the de-duplication key.
  • id (identify) contains SHA-256 hashes only – plaintext contact data never travels through the public endpoint. ac (_fbp, _fbc, _ttp) is only present after consent.
  • A batch is at most 16 KB and 1–5 pageviews; the SDK shrinks oversized state.

The exact zod description lives in @3x/core (schemas/wire.ts); the server validates with a clamping validator that drops what it does not understand instead of rejecting the batch.

Processing order

  1. Body size (413) → JSON (400) → key format and checksum (400) → key lookup (403).
  2. Proxy trust: a valid x-3x-proxy-key lets the request carry the visitor's IP and country in x-3x-client-ip / x-3x-client-country; otherwise the IP comes from the socket or the deployment's trusted headers (Environment).
  3. Origin must be an allowed origin of the project (403).
  4. Bots (user agent) and zero-viewport clients are acknowledged with 204 and not stored.
  5. Validation and the event-name cardinality guard; blocked event names are dropped.
  6. Rate limit per IP (429), database availability, project quotas and daily budgets – all of them answer 204 when they skip writing.
  7. The visitor hash sha256(daily salt ‖ ip ‖ ua ‖ project) is computed in memory only; IP and user agent are never stored. Salts are deleted daily.
  8. One transaction writes sessions, pageviews, events, video plays and identify leads, then derives flags, goals and the relevance score.
  9. In the background: conversion triggers, the event catalog, cleanup and retries.

Debugging

Outside production the API adds an x-3x-debug header with the session, stitch kind and whether each pageview state was applied. In production use the dashboard's live view, GET /v1/projects/{project}/events/live or the MCP tool events_tail.