Skip to main content
Platform feature. Requires WANIWANI_API_KEY on the server. Browser surfaces authenticate with a public token (wwp_...) or an injected widget token instead. About the Platform.
Events are a single typed taxonomy, sent through one track surface that is identical everywhere it appears:
That same track exists on four surfaces, each of which attaches session identity for you: The first three need no identity plumbing at all. Only the top-level backend client requires you to say who the event belongs to. See Sessions for the identity model and Widgets for the browser surfaces.

The event catalog

The EventType union is a closed, typed set, exported at runtime as EVENT_TYPES from @waniwani/sdk. These are all the events: Type shapes for every property interface live in the event schema reference. Placement guidance (which event belongs at which flow node) lives in Instrumentation. Any event name outside this table is rejected: the TrackEvent union will not typecheck it, and the ingest API does not power funnels with it. Model your funnel with the events above instead of inventing names.

What counts as a qualified lead

lead_qualified means the conversation produced a concrete handoff toward your business. A lead is qualified whenever the user:
  • is redirected to your pricing page or to your own website,
  • books a call or a meeting,
  • is sent an email (a quote recap, a follow-up, a booking confirmation),
  • or otherwise meets an explicit qualification bar you defined (finished the qualifying questions, requested a demo, matched your target profile).
Knowing who the user is does not qualify them. When you learn an email, a phone number, or a first and last name, call identify(): that attaches identity to the session and is the join key for later attribution, but it does not mean the lead is qualified yet. identify says “we know who this is”; lead_qualified says “this person is worth your sales attention”. Most funnels emit both, at different moments. See Identify and Instrumentation for placement. The chat backend also logs chat.user_message and chat.assistant_message server-side for every exchange; you never send those from code. Everything on this page is platform ingest: events sent to Waniwani to power funnel analytics. The chat widget also offers a separate host-callback channel — onEvent — that mirrors widget lifecycle events into your own page-side analytics (Amplitude, Segment, gtag) without touching the Waniwani ingest. See Widget events (onEvent).

Revenue helpers

The five revenue funnel events have flat typed helpers on track (there is no track.revenue.* namespace). Each accepts its event’s properties plus the shared tracking context (sessionId, externalUserId, visitorId, meta). occurredAt on converted backdates an off-platform sale to when it actually happened; the event itself may arrive months later.

Sending events

From a tool handler or flow node

Wrap the server once with withWaniwani(server), then use the scoped client from the handler context. Session identity is already attached; you pass nothing.
The scoped client also exposes the resolved session id as waniwani.sessionId. Store it with your own records (a lead, an order) and you can attribute an off-platform event back to the conversation later.

From a widget or the chat embed

The browser surfaces expose the same track. See Widgets for setup; the calls are identical:

From your backend

Create one top-level client (for example in lib/waniwani.ts) and pass identity explicitly on every call. This is how an off-platform conversion finds its original lead:
If you stored the scoped client’s sessionId during the conversation, sessionId: storedSessionId works as the join key too.

The identity rule

Every event must carry a sessionId, an externalUserId, or a visitorId. The ingest API rejects events with none, and the SDK warns at enqueue time. The scoped client, the widget hook, and the chat embed satisfy this automatically; the top-level backend client is the one place you pass identity yourself.

Return value

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.

Flushing

The transport flushes on a timer and on batch size. In long-running processes you do not need to call flush() yourself. In serverless functions, call it before the function returns:
Or stop the transport entirely:
Browser surfaces additionally flush with keepalive requests when the page is hidden or unloading, so events survive navigation.