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

# Events

> Events, called steps, are named, timestamped records of user actions. How to track them, what the payload looks like, and how to name them.

An **event** is one thing a user did: a name, optional properties, and a timestamp taken the moment you call `step()`. Pageviews are events too, named `$pageview` and recorded for you when tracking is on; [Pageviews and sessions](/concepts/pageviews) covers those. Events queue in memory and leave in batches, so tracking costs the page a few requests a minute at most.

## Tracking an event

Call `step()` with a name and an optional properties object:

```ts theme={null}
Wendung.step('checkout_started', {
  cart_total: 149.99,
  item_count: 3,
  currency: 'USD',
})
```

The method signature is:

```ts theme={null}
Wendung.step(name: string, properties?: Record<string, unknown>)
```

* **`name`**: a string between 1 and 100 characters that identifies the action.
* **`properties`**: an optional plain object with key-value metadata. Every value must be JSON-serializable.

<Note>
  Event names must be **1–100 characters** and properties a plain object (class instances and top-level arrays are rejected). Invalid calls log a `[Wendung - Warning]` and are ignored; nothing throws. Circular references surface later, at serialization time: the whole batch is dropped with a `Failed to serialize batch` warning.
</Note>

## Event payload structure

Each `step()` call snapshots the current session ID, identity, and page context at track time. The queued event takes this shape:

```json theme={null}
{
  "id": "0f8e2c1a-5b7d-4e3f-9a2b-6c1d8e4f0a3b",
  "name": "checkout_started",
  "timestamp": "2026-04-17T10:23:45.123Z",
  "sessionId": "9b2f4d6e-1a3c-4f5b-8d7e-2c4a6b8d0e1f",
  "identity": {
    "userId": "user_42",
    "traits": { "plan": "pro" }
  },
  "context": {
    "sdkName": "@wendung/sdk",
    "sdkVersion": "0.1.0",
    "pageUrl": "https://app.example.com/checkout",
    "referrer": "https://app.example.com/cart"
  },
  "properties": {
    "cart_total": 149.99,
    "item_count": 3,
    "currency": "USD"
  }
}
```

## Full request payload

When a batch is flushed, the SDK POSTs an `EventRequestPayload` to the ingest endpoint. Your API key is sent in the `X-API-Key` header, not in the body:

```json theme={null}
{
  "type": "event",
  "batchId": "3c5e7a9b-2d4f-4a6c-8e0b-1f3d5c7e9a2b",
  "sessionId": "9b2f4d6e-1a3c-4f5b-8d7e-2c4a6b8d0e1f",
  "identity": {
    "userId": "user_42",
    "traits": { "plan": "pro" }
  },
  "events": [ /* events as shown above */ ],
  "sentAt": "2026-04-17T10:23:50.000Z",
  "context": { /* context of the first event in the batch */ }
}
```

`sentAt` reflects when the batch was dispatched. Per-event timing is preserved in each event's own `timestamp`, and each event carries its own identity and context snapshots.

## Naming events

Names are the contract between your code and your funnels, so pick a convention and stick to it:

<AccordionGroup>
  <Accordion title="Use snake_case">
    Lowercase words joined by underscores are the most portable format across analytics tools and databases.

    ```
    signup_completed   ✓
    SignupCompleted    ✗
    signup-completed   ✗
    ```
  </Accordion>

  <Accordion title="Use past-tense action verbs">
    Events describe things that already happened, so the name should reflect that.

    ```
    checkout_started    ✓
    checkout_completed  ✓
    start_checkout      ✗
    ```
  </Accordion>

  <Accordion title="Be specific and descriptive">
    Avoid generic names that are hard to distinguish in dashboards.

    ```
    plan_upgraded       ✓
    button_clicked      ✗  (too vague)
    ```
  </Accordion>
</AccordionGroup>

## Using properties

Properties are key-value metadata on one event, and they are what you filter and slice by later. Put things there that are specific to that moment. Anything true of the user in general belongs in `identify()` traits instead.

<CodeGroup>
  ```ts Good: event-specific context theme={null}
  Wendung.step('video_played', {
    video_id: 'vid_789',
    duration_seconds: 312,
    autoplay: false,
  })
  ```

  ```ts Avoid: user-level data belongs in traits theme={null}
  // Don't repeat user-level data on every event.
  // Call Wendung.identify() once instead.
  Wendung.step('video_played', {
    user_email: 'alice@example.com', // ✗
    user_plan: 'pro',                // ✗
    video_id: 'vid_789',             // ✓
  })
  ```
</CodeGroup>

All property values must be JSON-serializable. Strings, numbers, booleans, arrays, and nested plain objects are all valid. Class instances and functions are not.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.