Skip to content
TraceLead

What is sent to TraceLead

The session and lead beacons, what is stored, what never is, and retention.

POST /api/collect receives the consent-gated session, lead and contact click events the snippet sends (ADR-021, ADR-041). Schema: /schemas/collect.v1.json.

Request

  • Body: JSON, sent as text/plain so navigator.sendBeacon / fetch(keepalive) need no CORS preflight. Maximum 8 KB and 10 events per request.
  • No authentication: the site ID is a public identifier. Abuse reduction only: the Origin/Referer host must match a registered domain (same rule as the config endpoint), the site must be enabled (active, subscription servable), the per-site collect flags must allow the event type, and requests are limited to 300/min per IP (TRACELEAD_COLLECT_RATE_LIMIT). A coarse user-agent screen drops obvious crawlers; the user agent is not stored.
{
  "schemaVersion": 1,
  "siteId": "tl_k3j9x2m8n4p7q1r5s6t0",
  "snippetVersion": "1.1.0",
  "events": [
    {
      "type": "pageview",
      "vid": "9f1c…32 hex…", "sid": "0a7e…32 hex…",
      "ts": "2026-10-09T14:02:11Z",
      "path": "/pricing",
      "touch": {
        "channel": "paid_search", "source": "google", "medium": "cpc",
        "utm_campaign": "spring-sale", "gclid": "…",
        "landing_page": "/pricing?utm_source=google&utm_medium=cpc",
        "first_touch_at": "2026-10-09T13:58:40Z"
      }
    },
    { "type": "lead", "vid": "…", "sid": "…", "ts": "…", "path": "/contact", "formId": "contact-form", "touch": { "…": "…" } },
    { "type": "contact", "vid": "…", "sid": "…", "ts": "…", "path": "/contact", "kind": "phone", "touch": { "…": "…" } }
  ]
}
  • vid — random visitor id the snippet mints after consent and keeps in first-party storage for the attribution lifetime.
  • sid — random session id, renewed after 30 minutes idle.
  • path — pathname only; any query or fragment is discarded server-side.
  • touch — the flattened first-touch fields (the same map written into hidden inputs). Unknown keys are ignored; gclid/gbraid/wbraid/msclkid/ttclid/li_fat_id/twclid/fbclid values are only used to derive which click id was present. last_* keys (optional last touch, ADR-026) are ignored: the dashboard is first-touch only.
  • formId — lead events only: the submitted form's id or name attribute, or the id page code passed to tracelead('lead', formId) for a form inside an iframe (≤ 100 chars). Never field values.
  • kind — contact events only (ADR-041): phone, email, sms or whatsapp, from a click on a tel:, mailto:, sms:, wa.me or api.whatsapp.com link or from tracelead('contact', kind). Sent only when the site config has collect.contacts: true. The link's number, address or message is never read past its scheme or host and never sent; any other key on the event (an href, a number) is ignored. See Contact clicks.

Response

Always 204 No Content with Access-Control-Allow-Origin: * and Cache-Control: no-store — for stored events, dropped events, unknown sites, malformed bodies and internal failures alike. The customer page never sees an error and nothing about tenants can be probed. OPTIONS /api/collect answers the same way with Access-Control-Allow-Headers: Content-Type.

What is stored

Table Row Columns
visitor_sessions one per (site, sid) visitor id, session id, started/last seen, pageview count, entry path, up to 30 pathnames, converted flag, attribution snapshot, snippet version
lead_events one per lead event (duplicates for the same visitor + form within 2 s collapse) visitor id, session link, time, form id, page path, attribution snapshot, snippet version, and on sites with engagement counters sessions_before_lead (1–10000) and days_to_lead (0–400)
contact_clicks one per (site, sid, kind): repeats in a visit collapse visitor id, session id and link, time, kind, page path, attribution snapshot, snippet version. Not a lead: it never marks the session converted or changes a lead count

Engagement counters (ADR-034): two whole numbers the snippet computes in the browser and sends with lead events only, stored only when the site has them on; values outside the ranges are dropped. A beacon whose touch contains ga_client_id is rejected outright (still 204) and logged as a rejection with no value: the GA client id may be filled into a form but never sent here.

Attribution snapshot: channel, source, medium, utm_campaign, utm_term, utm_content, utm_id, click_id_type, referrer_host, landing_page (sensitive parameters removed again server-side), first_touch_at.

Never stored: names, emails, phone numbers, any form value, full URLs, fragments, IP addresses, user agents, click-id values. Timestamps more than an hour old or five minutes in the future are replaced by the server clock.

Retention

php artisan tracelead:purge runs daily: sessions, leads and contact clicks older than the site's data_retention_days (default 395, 30–730) are deleted, and all collected data of a team soft-deleted for more than TRACELEAD_DELETED_TEAM_RETENTION_DAYS (90) days is removed. After the same period the closed organisation itself is deleted (team, sites, settings, audit trail, subscriptions, report links, webhooks, invitations, API tokens); invoices stay in Stripe. --dry-run reports counts only.

Install health

Successful config fetches from allowed pages set sites.last_config_fetched_at; stored events set last_event_at and last_snippet_version. Both are throttled to one write per site per minute and hold no request data.