Events

An event is one structured record of one thing that happened in a browser. It is a log line, not a marketing metric.

Event shape

What the client sends:

{
  "project": "myapp/home",
  "session": "1782693098872-7xzwuxbzd",
  "action": "page_view.pricing",
  "payload": {
    "path": "/pricing.html",
    "_v": "2.0.0",
    "query": "?ref=nav",
    "full_path": "/pricing.html?ref=nav",
    "referrer": "https://news.ycombinator.com/"
  }
}
Field Description
project Project identifier, matched against your registration.
session Identifier from the client, or absent when session is disabled.
action The action name. Your vocabulary.
payload Arbitrary JSON, capped at 512 characters serialised.

The origin comes from the request headers and the timestamp is assigned on receipt. Neither is client-supplied.

Action naming

Action names are the vocabulary your rules, digests and journey graphs are written against. The convention is a dotted hierarchy, most general segment first.

page_view.home
page_view.docs_rules
user.signup.success
user.signup.failure
user.payment.request
user.payment.success
story.viewer.load.start
story.viewer.load.complete

The reason is search. Dotted names support prefix and infix matching over the event data once it is in your own pipeline:

  • user.payment. finds every payment action, whatever the outcome.
  • .success finds every successful terminal action, across features.
  • story.viewer. isolates one feature’s lifecycle.

That property is what makes the data useful to ingest. A flat vocabulary of unrelated strings requires a lookup table nobody maintains; a hierarchy is queryable by anyone who reads a few event names.

Three properties follow from it:

  • Related actions sort together in listings and in the digest.
  • Prefixes are matchable in rules without enumerating every variant.
  • Outcome is explicit. success and failure as terminal segments make drop-off legible: a session reaching user.payment.request with no user.payment.success is a visible failure in the transition graph.

The client follows the same convention for pages, deriving the segment after page_view. from the path. See the client reference.

Do not encode values in the action name. product.view.SKU12345 produces one action per product and a transition graph nobody can read. The value belongs in the payload; use pageNameFn to map identifier-carrying routes to a pattern.

Payload conventions

Under 512 characters. Longer payloads are truncated server-side, and truncation can remove trailing fields without warning. Put the fields you intend to query first.

Send values you will read. A payload field earns its place when a rule, an attribution rule, or your own analysis reads it. Fields nothing reads are storage cost.

Never send personal data. No email addresses, names, phone numbers, or free text a user typed. The event stream is not designed for personal data, its retention is not tuned for it, and putting it there makes you the controller of a dataset you did not intend to create.

Normalise paths. If your routes carry identifiers, send the pattern and the identifier separately:

logger.log("product.view", { route: "/products/:id", product_id: "12345" });

Raw paths produce unbounded cardinality in the landing page breakdown, where every product becomes its own row and the distribution says nothing.

Enrichment

Every event is enriched within seconds of arrival, adding three groups of fields available to rules and present in exports.

User agent

{
  "browser": { "family": "Chrome", "version": "142.0.0" },
  "os": { "family": "Mac OS X", "version": "10.15.7" },
  "device_type": "desktop",
  "is_mobile": false,
  "is_bot": false
}

is_bot drives the bot ratio in the digest and excludes crawler traffic from the referrer and session analyses. It is a user agent classification, so a crawler presenting a browser user agent is not caught by it. Behavioural rules catch those: a session with nine signups from one user agent string in four minutes is a bot regardless of what it claims to be.

Location

{
  "countryCode": "GB",
  "region": "ENG",
  "city": "Bradford",
  "lat": "53.7960",
  "lon": "-1.7594",
  "timezone": "Europe/London",
  "org": "British Telecommunications"
}

Derived from the IP address, which is then discarded.

org is the network operator, and it is often the most informative field here. Hosting providers, VPN operators, Tor exit nodes and corporate networks are all visible in it, which makes it the field that separates real traffic from infrastructure.

City-level location from an IP address is approximate. Treat country and organisation as reliable, city as indicative.

Query parameters

Query strings are parsed into a structured field, so campaign parameters are available to rules without string handling:

/?utm_source=newsletter&utm_campaign=september

becomes queryable as utm_source and utm_campaign. The same applies to fbclid and gclid.

From events to sessions

Events sharing a session identifier within a window are aggregated into a session summary:

  • num_events and unique_actions
  • action_map, the ordered [action, offset_ms] sequence
  • actions, the flat list for existence checks
  • start_time, end_time, duration
  • the enrichment fields above

This is the object your rules see. Its full field list is in rules.

Because page identity is in the action name, action_map reads as a journey:

page_view.home        0
scroll                8400
page_exit.home        12100
page_view.pricing     12200
scroll                19000
page_exit.pricing     47200

That sequence is what the transition matrix is computed from, and it is why landing pages, exit pages and drop-off points are available without you defining a funnel.

Instrumentation coverage

A gap in your events is a gap in what can be measured, and it is invisible until you look. Two checks worth running after any change to your site:

clientlog --origin https://example.com --project myapp/home events names

Every action seen, with when it last occurred. An action missing from this list is not being emitted. An action with an old timestamp stopped being emitted, which usually means a refactor removed the call.

clientlog --origin https://example.com --project myapp/home \
  sessions list --limit 20

Confirms sessions are being built and tagged. A conversion tag that matches nothing for a week means the rule is wrong or the thing it measures stopped happening. Both are worth knowing.