> ## 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.

# Events

> How to send custom events from your MCP server.

<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>

An event is one call to `client.track()`. The SDK accepts a strongly typed `TrackEvent` shape and maps it to the canonical Events API V2 envelope before sending.

For the full type-level reference (the `TrackEvent` shape, the `EventType` union, all `*Properties` interfaces), see [Event schema](/sdk/reference/event-schema). This page is the usage guide.

## Revenue helpers

The five revenue funnel events have flat typed helpers on `track`. Each accepts its event's properties plus the shared tracking context (`sessionId`, `externalUserId`, `meta`).

| Helper                                                          | Event             |
| --------------------------------------------------------------- | ----------------- |
| `track.leadQualified({ externalId?, email?, name?, source? })`  | `lead_qualified`  |
| `track.priceShown({ amount, currency, itemId?, label? })`       | `price_shown`     |
| `track.pricesCompared({ options: [{ id, amount, currency }] })` | `prices_compared` |
| `track.optionSelected({ id, amount, currency })`                | `option_selected` |
| `track.converted({ amount, currency, occurredAt? })`            | `converted`       |

The helpers are flat on `track` (there is no `track.revenue.*` namespace). Inside a flow node, call them on the scoped client from the handler context, which carries session identity automatically:

```ts theme={null}
.addNode({
  id: "push_lead",
  run: async ({ state, waniwani }) => {
    const lead = await crm.createLead({ email: state.email });
    waniwani?.track.leadQualified({
      externalId: lead.id,
      email: state.email,
      source: "mcp_chat",
    });
    return { leadId: lead.id };
  },
})
```

`lead_qualified` means the person met your qualification bar. Emit it once per flow run, at the node where qualification completes. Where each revenue event belongs in a flow is covered in [Instrumentation](/sdk/tracking/instrumentation).

## Recipes

### `quote.requested` / `quote.succeeded` / `quote.failed`

```ts theme={null}
await wani.track({ event: "quote.requested", meta: extra._meta });

try {
  const amount = await pricingEngine.quote(input);
  await wani.track({
    event: "quote.succeeded",
    properties: { amount, currency: "USD" },
    meta: extra._meta,
  });
} catch (err) {
  await wani.track({ event: "quote.failed", meta: extra._meta });
  throw err;
}
```

### `link.clicked`

```ts theme={null}
await wani.track({
  event: "link.clicked",
  properties: { url: "https://example.com/pricing" },
  meta: extra._meta,
});
```

### `purchase.completed`

```ts theme={null}
await wani.track({
  event: "purchase.completed",
  properties: { amount: 4999, currency: "USD" },
  meta: extra._meta,
});
```

## Return value

```ts theme={null}
const { eventId } = await wani.track({
  event: "quote.succeeded",
  properties: { amount: 99, currency: "USD" },
  meta: extra._meta,
});
```

`eventId` is stable and safe to log. It is assigned before the envelope leaves the process, so logging it tells you an event was accepted into the buffer, not that it reached the backend. `track()` returns as soon as the envelope is enqueued, before the network request completes.

## Sessions and users

Pass `meta: extra._meta` from inside a tool handler so the event is linked to the active MCP session. See [Sessions](/sdk/tracking/sessions). To attach a user identifier, pass `externalUserId` or call `client.identify()`. See [Identify](/sdk/tracking/identify).

## Flushing

The transport flushes on a timer and on batch size. In long-running processes, you typically do not need to call `flush()` yourself. In serverless functions, call it before the function returns:

```ts theme={null}
await wani.flush();
```

Or stop the transport entirely:

```ts theme={null}
const { timedOut, pendingEvents } = await wani.shutdown({ timeoutMs: 5000 });
```
