Event schema

Every event ingested by the collector conforms to one of nine types below. All events share a common envelope (visitorId, sessionId, timestamp, page, device), and type-specific extensions add the fields that are meaningful for that event.

TypeScript definitions for every event type ship in the @clickstream/shared-types package; importing from there gives you the exact surface described below.

Event types

TypeEmitterWhen
pageviewSDKEvery page load + SPA route change (if autoTrackPageviews: true).
clickSDKEvery trusted click on an element the SDK decides to capture (see auto-capture rules).
scrollSDKThrottled scroll-depth samples at 10 %, 25 %, 50 %, 75 %, 100 %.
formSDKForm submit, focus, blur, and field change (universal form capture).
customSDK / serverAnything you call trackEvent() on. Most common carrier for conversion events.
identifySDK / servertracker.identify(email) / server-side POST /v1/events with identify payload.
email_openserverPixel fetch from an email tracking pixel.
email_clickserverCampaign redirect hit (wraps the destination URL).
_consent_transitionSDKInternal — fires when the visitor changes consent state. Never carries page content.

Common envelope

Every event has these fields:

interface BaseEvent {
  type: EventType;                    // one of the types above
  visitorId: string;                  // first-party _cs_uid cookie value
  sessionId: string;                  // SDK session id (resets after 30 min idle)
  timestamp: number;                  // ms since epoch (server overrides if >5 min skew)
  page: {
    url: string;                      // full URL of the current page
    path: string;                     // pathname only
    title: string;                    // document.title at event time
    referrer?: string;                // document.referrer
  };
  device: {
    userAgent: string;                // navigator.userAgent (truncated to 512 chars)
    viewport: { width: number; height: number };
    fingerprint?: string;             // composite device fingerprint
    fingerprintConfidence?: number;   // 0–100 — confidence the fingerprint is stable
    gpuVendor?: string;
    gpuRenderer?: string;
    connectionType?: string;          // '4g', '3g', 'wifi', …
    pixelRatio?: number;
    timezone?: string;                // IANA, e.g. 'America/New_York'
    language?: string;                // 'en-US'
    platform?: string;                // 'MacIntel', 'iPhone', 'Linux'
  };
}

Type-specific fields

pageview

Same as the envelope, with no extensions. The server derives scroll depth, time-on-page, and FCP / DCL / load timings from the next scroll / _attention_summary / _performance events the SDK fires later in the session.

click

interface ClickEvent extends BaseEvent {
  type: 'click';
  element: {
    selector: string;                 // unique CSS selector (truncated to 256 chars)
    text?: string;                    // inner text (PII-scrubbed, truncated)
    href?: string;                    // for anchor clicks
    tagName: string;                  // 'BUTTON', 'A', 'DIV', …
    classList?: string[];
    id?: string;
  };
  clickX?: number;                    // viewport-relative X coordinate
  clickY?: number;                    // viewport-relative Y coordinate
}

Auto-capture rules: the SDK captures clicks on <button>, <a>, <input type="submit">, elements with role="button", and elements with data-track="1". Everything else is ignored unless you call trackClick(element) manually.

scroll

interface ScrollEvent extends BaseEvent {
  type: 'scroll';
  depth: number;                      // % of page scrolled (0–100)
}

form

interface FormEvent extends BaseEvent {
  type: 'form';
  action: 'submit' | 'focus' | 'blur' | 'change';
  formId?: string;                    // <form id=…> or selector-derived
  fieldCount?: number;                // number of inputs in the form
  formSelector?: string;              // unique selector for the form
  fieldName?: string;                 // for focus/blur/change events
  textExcerpt?: string;               // redacted text preview (universal form capture)
}

custom

interface CustomEvent extends BaseEvent {
  type: 'custom';
  name: string;                       // e.g. 'signup_started'
  category?: string;                  // e.g. 'conversion'
  action?: string;                    // e.g. 'click'
  label?: string;                     // e.g. 'cta_primary'
  value?: number;                     // numeric value, used for LTV modeling
}

identify

interface IdentifyEvent extends BaseEvent {
  type: 'identify';
  hem?: string;                       // SHA-256 hashed email (lowercase trim)
  hemMd5?: string;                    // MD5 hashed email (for LiveRamp/TTD)
  hashedPhone?: string;               // SHA-256(E.164 phone)
  customerId?: string;                // your CRM ID
  accountId?: string;                 // B2B account id
  // Click-ID attribution:
  gclid?: string; gbraid?: string; wbraid?: string;
  fbclid?: string; msclkid?: string; ttclid?: string; twclid?: string;
  sccid?: string; epik?: string; irclickid?: string; _kx?: string;
  dclid?: string; li_fat_id?: string;
  // Social login IDs:
  googleId?: string; facebookId?: string; linkedinId?: string; appleId?: string;
  // Mobile advertising:
  maid?: string; maidType?: 'idfa' | 'gaid';
  // Cross-site journey:
  clickstreamId?: string; referringClickstreamId?: string;
  // UTM attribution:
  utmSource?: string; utmMedium?: string; utmCampaign?: string;
  utmTerm?: string; utmContent?: string;
}

Hashing is client-side. The SDK hashes email + phone before they leave the browser. Raw PII never hits the wire (except on the decrypt flow, which is password-gated and audited).

email_open / email_click

interface EmailEvent extends BaseEvent {
  type: 'email_open' | 'email_click';
  campaignId: string;
  campaignCode: string;               // ClickStream cs_cid value
  linkedId?: string;                  // cs_lid — resolves to a known visitor
}

These events are emitted by the collector when the campaign-redirect endpoint is hit. The campaign system wraps every tracked link in an email; when the user clicks, the cs_cid + cs_lid parameters flow through to the collector and land as email_click events, then 302 to the destination.

Analytics Engine field mapping

The collector writes every event into the clickstream_events dataset in Cloudflare Analytics Engine. Field mappings (20 blobs + 20 doubles + 1 index):

Index: clientId (multi-tenant scope).

Blobs (string fields — pipe-delimited encoding to maximize the 20-field limit):

BlobFields
blob1event_type | event_name
blob2page_url
blob3page_path
blob4referrer
blob5session_id
blob6visitor_id
blob7device_type | browser | os | gpu
blob8country | city
blob9element_selector | element_text
blob10form_id | custom_category | custom_action | custom_label
blob11hmacHem | hemMd5 — primary identity key
blob12hmacPhone — secondary identity key
blob13customer_id | account_id | user_id | crm_contact_id | order_id
blob14clickstream_id | referring_clickstream_id
blob15maid | maid_type
blob16google | fb | linkedin | apple (social IDs)
blob17reserved (freed — was ad-tech IDs; operator sites have no vendor pixels)
blob18click IDs: gclid | fbclid | msclkid | ttclid | dclid | gbraid | wbraid | li_fat_id | campaignCode
blob19UTM: source | medium | campaign | term | content
blob20ip_hash (SHA-256 of IP; never the raw value)

Doubles (numeric fields):

DoubleField
double1timestamp (ms)
double2scroll_depth_percent
double3time_on_page_ms
double4, double5viewport_width, viewport_height
double6, double7click_x, click_y
double8form_field_count
double9custom_value
double10, double11, double12page_load_time_ms, dom_content_loaded_ms, fcp_ms
double13fingerprint_confidence (0–100)
double14, double15is_vpn, connection_type
double16, double17latitude, longitude
double18, double19bot_score, is_bot
double20reserved

The blob layout is stable across releases — events written under a given mapping always deserialize against the same schema, regardless of when they were ingested.

Ingestion endpoint

POST https://t.example.com/v1/events (use your registered first-party tracking domain).

Example request:

POST /v1/events HTTP/1.1
Host: t.example.com
X-API-Key: cs_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Origin: https://example.com
Content-Type: application/json

{
  "type": "pageview",
  "visitorId": "vis_abc123",
  "sessionId": "sess_xyz456",
  "timestamp": 1713797640000,
  "page": { "url": "https://example.com/pricing", "path": "/pricing", "title": "Pricing" },
  "device": { "userAgent": "Mozilla/5.0 …", "viewport": { "width": 1280, "height": 800 } }
}

Response (202):

{ "success": true, "accepted": 1, "received": 1, "timestamp": 1713797640592 }

See also