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

# Events overview

> The mental model behind identify + track.

Vibefollow's data model is two primitives: **users** and **events**.

<CardGroup cols={2}>
  <Card title="User" icon="user">
    Anyone you want to communicate with. Identify them once with an external ID (your primary key) and a bag of traits.
  </Card>

  <Card title="Event" icon="bolt">
    Something the user did, with optional properties. Events drive audience membership and trigger rules.
  </Card>
</CardGroup>

You send both from your backend. Everything else — drafting, scheduling, sending, audience matching — is downstream of these two calls.

## The contract

```ts theme={null}
// 1. Tell Vibefollow who the user is.
await vf.users.identify('usr_42', {
  email: 'jane@acme.io',
  name: 'Jane Doe',
  plan: 'trial',
});

// 2. Tell Vibefollow what they did.
await vf.users.signedUp('usr_42', { plan: 'trial', source: 'organic' });
```

<Note>
  Identify and track are independent. You can call `track` before `identify` for the same user; the events buffer until the user is seen and are then attached. Re-identifying with the same ID upserts traits in place.
</Note>

## What happens after you send an event

<Steps>
  <Step title="Enqueue">
    The event is enqueued and returns `202 Accepted` immediately.
  </Step>

  <Step title="Normalise">
    A background worker normalises it, attaches it to the user, and updates derived state (last-seen timestamp, audience membership flags).
  </Step>

  <Step title="Trigger evaluation">
    The hourly trigger evaluator runs all 8 rule kinds against every user, in case this event newly satisfies one.
  </Step>

  <Step title="Draft and send">
    If a rule matches and the project is in autopilot, a draft is generated, scheduled, and sent at deadline.
  </Step>
</Steps>

<Info>
  Event delivery is fire-and-forget from your side. The `202` confirms enqueue, not processing. If the worker can't make sense of an event — malformed properties, unknown user — it's quarantined for review in the dashboard rather than discarded.
</Info>

## Standard vs custom events

Vibefollow ships with **10 canonical lifecycle events** — signup, trial started, feature used, etc. These have first-class typed helpers in the SDK and they're what every default trigger rule looks for.

You can also emit **any name you want** through `events.track()`. Custom event names are accepted as-is and become usable in audience filters and custom trigger rules.

<CardGroup cols={2}>
  <Card title="Lifecycle events" icon="list" href="/events/lifecycle-events">
    The canonical 10 with typed helpers.
  </Card>

  <Card title="Custom events" icon="code-branch" href="/events/custom-events">
    `events.track()` and `events.batch()`.
  </Card>
</CardGroup>

## Idempotency

Every event POST carries an `Idempotency-Key` header (auto-generated by the SDK). The backend dedupes within a 24-hour window — safe to retry an `events.track()` call after a network blip without producing duplicate events.

The key is a v4 UUID; you can override it by passing `Idempotency-Key` manually if you're calling REST directly.

## Privacy and data retention

<AccordionGroup>
  <Accordion title="Identify traits" icon="user-shield">
    Identify traits are stored in the user's `traits` JSON column. PII (email, name) lives in dedicated columns under access control.
  </Accordion>

  <Accordion title="Event payloads" icon="database">
    Event payloads are stored verbatim in the events table. Don't put secrets in properties — treat them as data, not as a private channel.
  </Accordion>

  <Accordion title="Retention" icon="clock-rotate-left">
    Retention is project-configurable. Default is unlimited; talk to support to set a TTL.
  </Accordion>
</AccordionGroup>
