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

# Event Tracking Overview

> Send events from your MCP server to the Waniwani dashboard.

<Note>
  **Platform feature.** Requires `WANIWANI_API_KEY`. Works whether your MCP server is self-hosted or on Managed Hosting. [About the Platform](/sdk/platform/overview).
</Note>

The tracking module ships events from your MCP server to the Waniwani backend. You call `client.track()`, the SDK enqueues the event in memory, and a background transport batches and posts it to `POST /api/mcp/events/v2/batch`. Tracking never blocks your tool handler.

## Mental model

<CardGroup cols={2}>
  <Card title="Events" icon="bolt" href="/sdk/tracking/events">
    A typed record of something that happened (a tool call, a quote, a purchase). Has a name, properties, and correlation ids.
  </Card>

  <Card title="Sessions" icon="link" href="/sdk/tracking/sessions">
    A session groups events from one MCP conversation. The SDK reads the session id from MCP `_meta`, so you pass `meta: extra._meta` on every call.
  </Card>

  <Card title="Identify" icon="user" href="/sdk/tracking/identify">
    Attach an `externalUserId` to the current session so events get linked to a real user.
  </Card>

  <Card title="Automatic tool tracking" icon="wand-magic-sparkles" href="/sdk/configuration/wrap-server">
    `withWaniwani(server)` wraps every tool handler and emits `tool.called` for you with timing and status.
  </Card>
</CardGroup>

## Track your first event

```ts theme={null}
import { waniwani } from "@waniwani/sdk";

const wani = waniwani(); // reads WANIWANI_API_KEY from env

// Inside a tool handler
await wani.track({
  event: "quote.succeeded",
  properties: { amount: 1299, currency: "EUR" },
  meta: extra._meta, // associates the event with the current session
});

// In serverless runtimes, flush before the function returns
await wani.flush();
```

That is the whole happy path. Everything else on the following pages is detail.

## How the transport behaves

When you call `track()`, the SDK:

1. Normalizes the input into a canonical Events API V2 envelope (see [events-api-v2-contract](https://github.com/waniwani-ai/sdk)).
2. Generates a deterministic event id (`evt_<uuid>`).
3. Pushes the envelope on an in-memory buffer and returns `{ eventId }` immediately.
4. A background timer flushes the buffer every `flushIntervalMs` (default `1000`), or sooner when the batch fills (`maxBatchSize`, default `20`).
5. Retries transient failures (`408`, `425`, `429`, `5xx`) with exponential backoff, up to `maxRetries` (default `3`).
6. Stops retrying on `401` / `403` auth errors to avoid retry storms.

### Long-running vs. serverless

| Runtime                   | How to make sure events ship                                                                                   |
| ------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Long-running Node process | Nothing. The SDK attaches `beforeExit`, `SIGINT`, and `SIGTERM` hooks that call `shutdown()` automatically.    |
| Serverless / short-lived  | Call `await wani.flush()` before the function returns, or pass `flushAfterToolCall: true` to `withWaniwani()`. |

## Client API

```ts theme={null}
interface TrackingClient {
  track(event: TrackInput): Promise<{ eventId: string }>;
  identify(
    userId: string,
    properties?: Record<string, unknown>,
    meta?: Record<string, unknown>,
  ): Promise<{ eventId: string }>;
  flush(): Promise<void>;
  shutdown(options?: { timeoutMs?: number }): Promise<{
    timedOut: boolean;
    pendingEvents: number;
  }>;
}
```

<CardGroup cols={2}>
  <Card title="track(event)" icon="plus" href="/sdk/tracking/events">
    Enqueue an event.
  </Card>

  <Card title="identify(userId, properties?, meta?)" icon="user" href="/sdk/tracking/identify">
    Send a `user.identified` event and attach the user id to the session.
  </Card>

  <Card title="flush()" icon="water">
    Wait for the current buffer to drain.
  </Card>

  <Card title="shutdown({ timeoutMs? })" icon="power-off">
    Flush and stop the transport. Returns `{ timedOut, pendingEvents }`.
  </Card>
</CardGroup>

<Note>
  All four methods throw synchronously if no `apiKey` was resolved (from config or `WANIWANI_API_KEY`). If you instantiate `waniwani()` without an api key, the client is inert and calling `track()` will throw.
</Note>
