> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fourdos.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Events

> How engagement flows into 4D, and what happens to it once it's there

Events are how 4D learns what your members do. Everything downstream - tag
affinity, XP accrual, module integrations - is driven by events.

## Two ways in

**Browser tracking - `POST /v1/track`.** The public receptor for engagement
events from your sites and apps, authenticated with a publishable
`ek_live_…` key (like an analytics measurement id - origin allowlist and
rate limits bound abuse, not secrecy). Events are batched, up to 20 per
request, and accepted per-item: the response tells you for each event whether
it was `accepted`, a `duplicate` (already seen via its client-supplied
`event_id`), or `rejected` with a reason. This is the surface the
[engagement SDK](/sdk/engagement-sdk) wraps.

**Server ingest - `POST /v1/ingest`.** Installed modules push events
server-to-server with an `mk_live_…` credential and HMAC request signing.
Only relevant if you're building a 4D module.

## Anatomy of an engagement event

An engagement event is deliberately simple:

* **who** - the member, identified by *your* id for them
  (`member_external_id`) or the platform `user_id`. Events are only recorded
  for known members; an event that can't be attributed to one is rejected,
  not stored.
* **what** - an `action` (defaults to `engage`; e.g. `content_view`) plus
  free `metadata`.
* **tags** - up to 10 lowercase tags per event
  (`world_cup_26`, `england`, `kane`). Tags are the currency of the
  engagement system: every event increments the member's per-tag **affinity
  counters**, so over time you get a live map of what each member cares
  about. Normalization is strict (trimmed, lowercased) so `World_Cup_26` and
  `world_cup_26` converge on one counter instead of fragmenting it.
* **when** - an optional client timestamp, clamped server-side to the recent
  past.

Duplicate delivery is expected and safe: clients send a generated `event_id`
with each event and the server dedupes on it, so network retries never
double-count affinity.

## What happens next

Accepted events are written to your organization's event stream and
simultaneously published onto 4D's internal event bus as canonical
`engagement.*` events. That's where the rest of the platform picks them up -
most importantly the [XP engine](/concepts/xp), which evaluates your
organization's rules against each event and awards XP when one matches.
Webhook subscriptions and recent-event queries (30-day retention) are
available for pulling events back out.

## Consent-gated enrichment

Events are enriched only as far as the member has consented. The concrete
example: if (and only if) the member holds an active `data.geo` consent, the
platform stamps a coarse country/region onto their events, derived
transiently from the request - the raw IP address is never persisted. No
consent, no location. See [Consent](/concepts/consent).
