Platform feature. The browser surfaces authenticate with credentials they already hold: a widget token injected by
withWaniwani, or the chat widget’s public wwp_... token. No API key ships to the browser. About the Platform.track client as the server, with session identity attached automatically.
MCP-app widgets: useWaniwani()
For widgets rendered inside tool responses (ChatGPT apps, Claude MCP apps). Requirements: the MCP server is wrapped with withWaniwani(server), which injects the tracking endpoint, a short-lived widget token, and the session id into each tool response under _meta["waniwani/widget"].
useWaniwani() is host-agnostic: it takes that metadata as data and opens no connection to the widget host itself. How you hand it the metadata depends on your host.
skybridge-hosted widgets. Import the hook from the skybridge adapter entry and call it bare. It reads skybridge’s useToolInfo().responseMetadata and feeds it to the hook for you:
skybridge is an optional peer dependency of the SDK; a skybridge project already has it.
Any other host. Import from @waniwani/sdk/mcp/react and pass the tool-response _meta your host exposes as toolResponseMetadata:
One
widget_render event is emitted automatically when the hook initializes with resolved config, so “widget shown” exists as a funnel step even if you track nothing manually.
With no resolved endpoint and source (no metadata, no explicit options), the hook returns a no-op widget, so rendering never breaks.
Explicit configuration
When the widget cannot resolve config from the host (or you proxy events through your own backend), pass the endpoint directly.source is required in this mode so events attribute to a channel:
createTrackingRoute on your backend when you want zero Waniwani credentials in the browser.
Chat host pages: chat.track
The chat embed and WaniwaniChat hold the public token and the server-assigned session, so the host page can send funnel events with zero setup. Events carry the sessionId once the first exchange assigns one, and the anonymous visitorId before that.
With the <script> embed:
track and identify are absent on the bare ChatEmbed primitive, which is bring-your-own-backend and holds no Waniwani credential.
To mirror chat lifecycle events (opened, closed, message sent/received) into your own analytics instead, use the embed’s onEvent callback; it runs in the page’s JS context and carries the sessionId.
Custom surfaces: createFrontendClient
Both surfaces above are wrappers around one primitive, exported for anything they do not cover:
identity() at emit time so late-arriving ids (a session assigned mid-page) are picked up without recreating the client.
Delivery behavior
- Events batch in memory and flush on a short timer, on batch size, and with
keepaliverequests when the page is hidden or unloading. - Tracking never throws into the host page; failures are logged and dropped.
- On an auth failure (expired widget token, revoked public token) the transport stops and logs the reason once, instead of retrying forever.