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

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

FieldWhat 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 answer
conversationThe whole conversation so far, as { role, text, citations }[]. role is "user" or "assistant"

Panel

EventFires whenPayload
onModalOpenThe panel opens{ mode }: "ai" or "search"
onModalCloseThe panel closes{ mode }
onModeSwitchThe reader switches between the chat and Search tabs{ mode }: the tab now showing
onConsentAcceptThe reader accepts the consent screen{}
onConsentRejectThe reader declines it{}

Ask AI

EventFires whenPayload
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 Citation
onAskAICtaClickThe call-to-action button under an answer is clicked{ href, threadId?, questionAnswerId }

Hand-off

EventFires whenPayload
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.
EventFires whenPayload
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

EventFires whenPayload
onExitFeedbackSubmitThe reader answers the exit question{ helpful, threadId? }. helpful is true or false
onUserSatisfactionSubmitThe reader sends the 1–5 rating{ rating, comment?, threadId? }
See User satisfaction for when each survey appears.

Sending events to your analytics

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.