Skip to main content
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.

The TrackInput shape

import type { TrackInput } from "@waniwani/sdk";

// TrackInput = TrackEvent | LegacyTrackEvent
// Modern TrackEvent (preferred):
type TrackEvent = {
  event: EventType;                     // required, one of the built-in names
  properties?: Record<string, unknown>; // typed per event name
  meta?: Record<string, unknown>;       // MCP request _meta
  metadata?: Record<string, unknown>;   // free-form metadata on the envelope
  sessionId?: string;                   // explicit override, usually omit
  traceId?: string;
  requestId?: string;
  correlationId?: string;
  externalUserId?: string;
  eventId?: string;                     // supply your own id (idempotency)
  timestamp?: string | Date;            // defaults to now
  source?: string;                      // defaults to "@waniwani/sdk"
};
event
EventType
required
The event name. Must be one of the built-in EventType values listed below.
properties
Record<string, unknown>
Event payload. For built-in events, the type is narrowed (for example, quote.succeeded expects { amount?: number; currency?: string }).
meta
Record<string, unknown>
MCP request metadata, typically extra._meta inside a tool handler. This is where the SDK reads the session id from. See Sessions.
sessionId
string
Explicit session id override. Prefer passing meta instead.
externalUserId
string
External user identifier. Equivalent to calling identify() inline. See Identify.
eventId
string
Client-generated id. If you omit it, the SDK generates evt_<uuid>. Supplying your own enables idempotent resends.
track() returns { eventId } as soon as the envelope is enqueued, before the network request completes.

Built-in event types

EventType is a closed union exported from @waniwani/sdk. Only these names are accepted by track().
Event nameTyped propertiesNotes
session.startednoneEmitted by the WaniWani backend when it sees a new session id. Do not send it yourself.
tool.calledToolCalledPropertiesEmitted automatically by withWaniwani(server). Do not send it yourself.
quote.requestednoneTop of a pricing funnel.
quote.succeededQuoteSucceededPropertiesA quote was produced.
quote.failednoneA quote could not be produced.
link.clickedLinkClickedPropertiesAn outbound link was followed.
purchase.completedPurchaseCompletedPropertiesA purchase finished.
user.identifiednonePrefer client.identify() over sending this directly.
import type {
  ToolCalledProperties,
  QuoteSucceededProperties,
  LinkClickedProperties,
  PurchaseCompletedProperties,
} from "@waniwani/sdk";

interface ToolCalledProperties {
  name?: string;
  type?: "pricing" | "product_info" | "availability" | "support" | "other";
}

interface QuoteSucceededProperties {
  amount?: number;
  currency?: string;
}

interface LinkClickedProperties {
  url?: string;
}

interface PurchaseCompletedProperties {
  amount?: number;
  currency?: string;
}
The SDK also defines widget_render, widget_click, widget_link_click, widget_error, widget_scroll, widget_form_field, and widget_form_submit event types. These are emitted automatically by the widget runtime (useWaniwani() in a browser widget) and are not intended to be sent from server code.

Recipes

quote.requested / quote.succeeded / quote.failed

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;
}
await wani.track({
  event: "link.clicked",
  properties: { url: "https://example.com/pricing" },
  meta: extra._meta,
});

purchase.completed

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

Return value

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.

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:
await wani.flush();
Or stop the transport entirely:
const { timedOut, pendingEvents } = await wani.shutdown({ timeoutMs: 5000 });
The SDK also accepts a legacy shape with eventType, toolName, toolType, quoteAmount, quoteCurrency, linkUrl, purchaseAmount, and purchaseCurrency fields. It is kept for backward compatibility with pre-V2 integrations. New code should use the modern { event, properties, meta } shape shown above.