Skip to main content
Ask your AI

Understanding walkerOS Events

Overview​

walkerOS events are self-describing, stateless, vendor-agnostic data structures. They capture user interactions in a standardized format that can be transformed for any destination.

Core principle: Events describe WHAT happened, not WHERE it goes. Stateless. Self-describing. Industry-agnostic.

Entity-Action Naming (Critical)​

STRICT REQUIREMENT: All events use "entity action" format with space separation.

// Correct
'page view';
'product add';
'order complete';
'button click';

// Wrong
'page_view'; // underscore
'pageview'; // no separator
'purchase'; // no entity
'add_to_cart'; // wrong format

Parsing: const [entity, action] = event.split(' ')

  • Entity: Noun (page, product, user, order, button)
  • Action: Verb (view, add, complete, click, login)

Event Properties​

See packages/core/src/types/walkeros.ts for canonical types (Event interface, plus event helpers in event.ts).

PropertyTypePurposeExample
namestring"entity action" format"product view"
dataobjectEntity-specific properties{ id: "P123", price: 99 }
contextobjectState/environment info (optional){ stage: ["checkout", 1] }
globalsobjectGlobal properties{ language: "en" }
userobjectUser identification{ id: "user123" }
nestedarrayRelated entities (optional)[{ entity: "product", data: {...} }]
consentobjectConsent states{ marketing: true }
idstringW3C span_id, 16 lowercase hex chars, generated by collector"0123456789abcdef"
timestampnumberAuto-generated Unix ms1647261462000
entitystringParsed from name"product"
actionstringParsed from name"view"
source.typestringSource kind (browser, dataLayer, cookiefirst, ...)"browser"
source.platformstringRuntime platform (web, server)"web"
source.schemastringSource-emitted schema/version (optional)"datalayer-v2"
source.tracestringRun-scoped W3C trace_id, shared by every event of a run"0123...cdef" (32 hex)
source.countnumberPer-run emission sequence (1, 2, 3, ...)1
source.releaseobjectPer-flow config release map, accumulates across crossings{ web: "3", server: "5" }

data Property​

Entity-specific properties. Schema-free but consistent within entity type.

// product entity
data: { id: "P123", name: "Laptop", price: 999, currency: "USD" }

// page entity
data: { title: "Home", path: "/", referrer: "https://..." }

context Property​

Hierarchical state information. Format: { name: [value, order] }. Optional.

context: {
stage: ["checkout", 1], // checkout stage, first step
test: ["variant-A", 0], // A/B test variant
group: ["premium", 2] // user segment
}

globals Property​

Properties that apply to ALL events in the session.

globals: {
language: "en",
currency: "USD",
environment: "production"
}

nested Property​

Related entities captured together. Optional.

// Order with line items
nested: [
{ entity: 'product', data: { id: 'P1', quantity: 2 } },
{ entity: 'product', data: { id: 'P2', quantity: 1 } },
];

user Property​

User identification across sessions.

user: {
id: "user123", // Your user ID
device: "device456", // Device fingerprint
session: "sess789", // Session ID
hash: "a1b2c3...", // Hashed identifier (for example a hashed email)
optout: false, // User opted out of tracking (boolean)
segment: "vip" // Custom keys are allowed, each holds a property value
}

The declared fields keep their types: email must be a valid address, so a hashed email goes to hash, never to email. Any other key is a custom user attribute with a property value and passes the validate transformer's format: true check.

source Property​

Where the event came from. The collector enriches the event with source data based on which source emitted it.

source: {
type: 'browser', // source kind (browser, dataLayer, cookiefirst, ...)
platform: 'web', // runtime: 'web' or 'server'
version: '4.0.0', // external source-emitter version (collector no longer sets this)
schema: 'datalayer-v2', // optional schema/version emitted by the source
trace: '0123...cdef', // run-scoped W3C trace_id, groups all events of a run
count: 1, // per-run emission sequence (1, 2, 3, ...)
release: { web: '3' }, // per-flow config release map, accumulates web to server
url: 'https://...', // page URL (web only, set by web-context transformer)
referrer: '...', // page referrer (web only)
tool: 'cli', // tool that produced the event (optional)
command: 'simulate', // command name (optional)
}

Steps may add their own keys to source (the validate transformer writes valid, GA4 decoding adds pageLoadId and hitSequence); each holds a property value.

CMP and other non-page sources do NOT set source.url/source.referrer - that's the responsibility of a web-context transformer.

source.trace is the run grouping key: the collector mints a fresh run-scoped trace_id on each run and stamps it (plus a per-run source.count) on every event when absent. It is preserved unchanged when an event is forwarded from web to server, so the whole pipeline shares one trace.

source.release records which config handled the event, keyed by flow name. Each flow the event passes through adds its own entry (that flow's config release), so an event captured on the web and processed on the server carries both flow releases, and a warehouse row shows exactly which config version touched it. This is distinct from source.version, the source package version set only by external source emitters (the collector no longer stamps it).

Migration from v3​

If you have existing v3 events or configs, here is the v4 mapping:

v3v4
event.id = "<ts>-<rnd>-<seq>"event.id = W3C span_id (16 lowercase hex chars)
event.versionremoved - see source.version and source.schema
event.groupremoved - use source.trace (run-scoped trace_id) for correlation
event.countremoved - use source.count (per-run emission sequence)
event.source.idevent.source.url (when it meant page URL)
nested: [{ type, data }]nested: [{ entity, data }] (Entity.entity replaces .type)

Design Principles​

Statelessness​

Events are immutable snapshots. They don't reference previous events or maintain state.

Self-Describing​

Events contain all context needed to understand them. No external lookups required.

Vendor-Agnostic​

Events use generic concepts (product, order) not vendor-specific (GA4 item, FB content).

Transformation to vendor formats happens in mapping, not in event creation.

Creating Events​

elb takes a partial event: name is required, every other field is optional. The collector adds id, timestamp, entity, action and source, and merges its own globals, user and consent key by key (the event's values win).

import { startFlow } from '@walkeros/collector';

const { elb } = await startFlow({
/* destinations */
});

const result = await elb({
name: 'product add',
data: { id: 'P123', price: 99 },
context: { stage: ['cart', 1] },
globals: { currency: 'USD' },
});

// A rejected event reaches no destination, e.g. 'Event name is invalid'
if (!result.ok) console.error(result.error);

Every event uses this partial event form. walker commands keep their string name: elb('walker consent', { marketing: true }).

Source Files:

Documentation:

💡 Need implementation support?
elbwalker offers hands-on support: setup review, measurement planning, destination mapping, and live troubleshooting. Book a 2-hour session (€399)