The widget reports what readers do as events: the panel opening, a question asked, an answer finished, a source clicked, a rating given. Subscribe to send them to Segment, Mixpanel, Amplitude, Google Analytics or your own endpoint.
Subscribing
window.Kelu
window.KeluWidget
function handler ( payload ) {
console . log ( payload . question );
}
window . Kelu ( "onAskAIQuerySubmit" , handler ); // subscribe
window . Kelu ( "onAskAIQuerySubmit" , handler , "remove" ); // unsubscribe
An unknown event name logs a warning and subscribes nothing.
A handler that throws is logged and skipped. Other handlers and the widget carry on.
Subscriptions last for the page. They survive unmount(), render() and destroy().
To catch early events , subscribe through a preinitialization stub .
In React, unsubscribe in the effect’s cleanup (with the stub on the page, since the effect can run before the script loads):
useEffect (() => {
const handler = ({ question }) => analytics . track ( "Docs AI question" , { question });
window . Kelu ( "onAskAIQuerySubmit" , handler );
return () => window . Kelu ( "onAskAIQuerySubmit" , handler , "remove" );
}, []);
Common payload fields
Field What it is threadIdThe conversation’s id. Absent until the first answer arrives, so the first question’s submit event has none questionAnswerIdThe id of one question and its answer. Every event of that turn carries the same value: the submit, the finished answer, a copy, a vote, a link or source click. Use it to join them up. A retry keeps the same id Citation{ title, url, snippet?, index? }. index is the [n] number the source has in the answerconversationThe whole conversation so far, as { role, text, citations }[]. role is "user" or "assistant"
Panel
Event Fires when Payload onModalOpenThe panel opens { mode }: "ai" or "search"onModalCloseThe panel closes { mode }onModeSwitchThe reader switches between the chat and Search tabs { mode }: the tab now showingonConsentAcceptThe reader accepts the consent screen {}onConsentRejectThe reader declines it {}
Ask AI
Event Fires when Payload onAskAIQuerySubmitA question is sent: typed, carried in from your search box, or from code (ask(), open({ submit: true })) { threadId?, questionAnswerId, question }onAskAIExampleQuerySubmitA starter question is clicked. Fires instead of onAskAIQuerySubmit { threadId?, questionAnswerId, question }onAskAIAnswerCompletedAn answer finishes without error { threadId?, questionAnswerId, question, answer, citations, conversation }onAskAIGenerationStopAn answer is stopped before it finishes: the Stop generating button, or reset() or unmount() while it is being written { threadId?, question, conversation }onAskAIConversationResetThe conversation is cleared, by New conversation or reset(). Carries the conversation as it was before { threadId?, conversation }onAskAIFeedbackSubmitThe reader presses thumbs up or down, or presses the same thumb again to take the vote back { reaction, threadId?, questionAnswerId, question, answer }. reaction is "up", "down" or "withdrawn"onAskAIAnswerCopyThe reader copies an answer { threadId?, questionAnswerId, question, answer }onAskAILinkClickA link inside an answer is clicked { href, threadId?, questionAnswerId }onAskAISourceClickA source under Sources is clicked { source, threadId?, questionAnswerId }. source is a CitationonAskAICtaClickThe call-to-action button under an answer is clicked { href, threadId?, questionAnswerId }
Hand-off
Event Fires when Payload onAskAIHandoffOpenThe Create ticket button is clicked { threadId?, conversation, transcript, triggersMatched }onAskAIHandoffSubmitRight after onAskAIHandoffOpen, on the same click, with the same payload { threadId?, conversation, transcript, triggersMatched }onAskAIHandoffCancelNever. The widget has no hand-off form to cancel; the name is accepted so code written for kapa.ai does not warn { threadId?, conversation }
transcript is the conversation as text, one Question: or Answer: paragraph per turn, ready to paste into a ticket.
triggersMatched lists the triggers that showed the button on that answer, for example ["uncertain"].
With data-handoff-mode="custom" the button opens nothing itself; your handler for these events does the hand-off. See Conversation hand-off .
Search
Event Fires when Payload onSearchResultsCompletedResults appear on the Search tab. Not when the search fails, and not for the matches shown while typing a question { query, searchResults }. searchResults is { title, url }[]onSearchResultClickA result on the Search tab is clicked { query, searchResult, rank }. searchResult is { title, url }; rank counts from 0
Surveys
Event Fires when Payload onExitFeedbackSubmitThe reader answers the exit question { helpful, threadId? }. helpful is true or falseonUserSatisfactionSubmitThe reader sends the 1–5 rating { rating, comment?, threadId? }
See User satisfaction for when each survey appears.
Sending events to your analytics
Segment
Mixpanel
Google Analytics
Your own endpoint
window . Kelu ( "onAskAIQuerySubmit" , ({ question , threadId }) => {
analytics . track ( "Docs AI Question" , { question , threadId });
});
window . Kelu ( "onAskAIAnswerCompleted" , ({ question , citations }) => {
analytics . track ( "Docs AI Answer" , { question , sources: citations . length });
});
question, answer, conversation and transcript are your readers’ own words. Send them to a third-party tool only where your privacy policy allows it.