Custom Tracking Scripts

View as Markdown

Chat Logs and Analytics report an Agent’s activity inside Console. Custom Scripts sends that activity to your own analytics instead, so an Agent appears beside everything else you already measure.

It is one field on the Agent’s UI Settings page, and it holds JavaScript that runs on the Agent’s pages. Suppose your website reports to Google Analytics. Paste the same tag into this field and the Agent’s visitors land in the same account, under the same reports, without anything else to set up.

A tag like that counts visits and not much more, because nothing tells it what the parts of a chat page mean. So the Agent says what a visitor is doing, as browser events your script can listen for: a question asked, a Call to Action clicked, a guardrail fired.

Purpose

Add your own analytics or tracking code to an Agent’s pages, and listen for the Agent’s events so your reports show what visitors did rather than only that they arrived.

Prerequisites

  • An Agent with Standalone Web UI or Embedded Web Widgets turned on. Without one of those the Agent has no page for a script to run on, and no UI Settings to put it in. See Standalone Web UI and Embedding On a Website.
  • Permission to edit Agents for your team.
  • The tracking code you want to add, and somewhere for it to report to.

Anything you paste here runs on the Agent’s pages for every visitor, exactly as written and with nothing removed. A mistake breaks the page for everybody, and a third-party tag can see everything on it. Paste only code you trust, and check your obligations around consent and visitor privacy before you add tracking.

Add a Script

Select Agents in the left-hand navigation, open your Agent, then choose UI Settings and select the Scripts tab.

Paste your code into Custom Scripts, including its <script> tags, then select Save Changes. Two things go in here, and you can put both in the same field: the snippet your analytics provider gave you, pasted unchanged, and the listeners that record what a visitor did, which Listen for an Event shows you how to write.

The Scripts tab of the Seeker Agent's UI Settings page in Console, with a tracking script in the Custom Scripts field and the start of the Events reference below it.

Beneath the Custom Scripts field, Console lists a reference of every event the Agent publishes, grouped by source. It mirrors the Events section below, so you can write a listener without leaving the screen.

Your custom script code is placed on the Agent’s pages and runs as the page is parsed, before the chat interface appears. That is early enough for a top-level window.addEventListener to catch every event below, so you do not need to wait for anything to be ready.

Put scripts here and nowhere else. The rich text fields on Calls to Action, guardrails and other messages are for content shown inside the conversation, and their HTML is cleaned before it reaches a visitor. A <script> tag in one of those is removed and never runs.

Listen for an Event

The Agent dispatches its events on window, each one named with an apg: prefix. Register a listener for the ones you care about and read the rest from event.detail.

<script>
window.addEventListener('apg:completion:finish', (e) => {
const { completionId, agentId, sessionId } = e.detail;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'agent_answer',
completion_id: completionId,
agent_id: agentId,
});
});
</script>

Every event carries the same identifiers, so you can stitch one visitor’s activity together whichever events you listen for.

FieldIs
tsWhen it happened, in milliseconds since the epoch
deviceIdAn anonymous identifier for the browser, kept between visits
sessionIdThis visit
conversationIdThis conversation
userIdThe identifier your own integration supplied for the person

The three identifiers are created the first time a visitor needs one, so they are there from the very first event rather than arriving partway through. They are held in the browser’s own storage, so they come through as null when a visitor has that blocked. userId is null unless your integration set one, which most Agents never do.

Event details are plain JSON values, and never contain the HTML of anything shown to the visitor. Where an event refers to something with a body, such as a Call to Action, it gives you the identifier and leaves you to look the content up.

Events

Asking a Question

EventFires whenAlso carries
apg:starter:clickA visitor selects one of the Agent’s starter promptsagentId, questionId, text
apg:prompt:submitA visitor sends a question they typedagentId, text, source (form)

A starter prompt sends a question without going through the input box, so apg:starter:click fires instead of apg:prompt:submit, not as well as. Counting both as one “question asked” means listening for both. See Starter Prompts.

questionId names the starter prompt that was selected, and stays the same every time anyone selects it. It is not the identifier of the exchange that follows, which arrives separately as completionId below.

Answering It

EventFires whenAlso carries
apg:completion:startThe Agent begins working on a questionagentId, client (standalone or embedded), language
apg:completion:finishThe Agent finishes its answeragentId, completionId, messageId
apg:completion:errorThe answer failedagentId, message

completionId is the identifier of the exchange, and it is what to record if you want to find the same conversation later in Chat Logs. It does not exist yet when apg:completion:start fires, and it is not reported on an error, so take it from apg:completion:finish.

language is the language code the exchange is in, or auto on an Agent that detects the language of each question for itself. See Configuring Languages.

What a Visitor Does With an Answer

Each of these fires on the answer the visitor acted on, and carries agentId and completionId and nothing else, apart from the one noted below.

EventFires when
apg:completion:like / apg:completion:unlikeThe visitor likes an answer, or takes the like back
apg:completion:flag / apg:completion:unflagThe visitor flags an answer as a problem, or takes the flag back
apg:completion:copyThe visitor copies an answer
apg:completion:shareThe visitor shares an answer
apg:completion:sources:openThe visitor opens the sources behind an answer
apg:completion:sources:closeThe visitor closes them again

apg:completion:sources:open also carries sourceCount, the number of Sources the answer cited. A like and a flag are the same two events a visitor’s action writes into Chat Logs, so you can count them here and read them there.

Calls to Action

EventFires whenAlso carries
apg:cta:displayA Call to Action is shownagentId, completionId, ctaId, displayMode (footer or modal), evaluationId, source
apg:cta:openIts dialog opensagentId, completionId, ctaId, reason (button or auto)
apg:cta:clickThe visitor selects something inside itagentId, completionId, ctaId, displayMode
apg:cta:closeIts dialog closesagentId, completionId, ctaId

apg:cta:display fires once per answer per Call to Action, so a visitor scrolling back up past one they have already seen does not count twice.

source says how the Call to Action was chosen: metadata when it arrived with the answer, and match when it was matched after the answer had been written. reason on apg:cta:open distinguishes a visitor opening the dialog from the Agent opening it for them.

Guardrails

EventFires whenAlso carries
apg:guardrail:displayA guardrail message is shownagentId, completionId, guardrailId, kind, action, expiresAt, source (live or hydrate)
apg:guardrail:redirectA guardrail sends the visitor to another addressagentId, completionId, guardrailId, redirectUrl
apg:guardrail:lockA guardrail locks the visitor out for a periodagentId, completionId, guardrailId, expiresAt, banForMinutes
apg:guardrail:endA guardrail ends the conversationagentId, completionId, guardrailId

source on apg:guardrail:display is worth checking before you count anything. live means the guardrail has just fired. hydrate means the visitor reloaded a page while still locked out and is being shown the same message again, which is not a new event in your funnel.

apg:guardrail:redirect fires immediately before the browser leaves the page, so a listener that reports it asynchronously may not finish in time. Use navigator.sendBeacon for that one.

Voice and Dictation

An Agent uses one of these, never both. A voice to voice Agent holds a spoken conversation; every other Agent offers browser dictation, which turns speech into text in the input box.

EventFires whenAlso carries
apg:voice:start / apg:voice:endA spoken conversation starts or endsagentId, mode (livekit or realtime), providerKey, modelType. The end adds reason (user, auto or error)
apg:dictate:start / apg:dictate:endBrowser dictation starts or endsagentId, engine (browser). The end adds reason (user, result, error or auto)

The Rest of the Page

EventFires whenAlso carries
apg:info:open / apg:info:closeThe visitor opens or closes the Agent’s information dialogagentId
apg:disclaimer:open / apg:disclaimer:closeThe visitor opens or closes the disclaimeragentId
apg:footer:ctaThe visitor selects the footer linkagentId, url

Where These Events Do and Do Not Reach

These are browser events, so they exist only where the Agent has a browser page: its own address and the embedded widget. Conversations that arrive through a messaging channel or through the Agent API never touch a page, so nothing here fires for them. Use Notifications to be told about those.

The embedded widget runs in an iframe, and events stay inside it. Nothing is passed out to the page hosting the widget, so a tag installed on your own site sees none of this. Put the tracking in Custom Scripts instead, which runs inside the iframe where the events are, and let it report to the same account your site reports to.

Check That It Works

Open the Agent in a browser, open your browser’s developer tools, and paste this into the console. It prints every apg: event as it happens.

[
'apg:prompt:submit', 'apg:starter:click',
'apg:completion:start', 'apg:completion:finish', 'apg:completion:error',
'apg:completion:like', 'apg:completion:unlike',
'apg:completion:flag', 'apg:completion:unflag',
'apg:completion:copy', 'apg:completion:share',
'apg:completion:sources:open', 'apg:completion:sources:close',
'apg:cta:display', 'apg:cta:open', 'apg:cta:click', 'apg:cta:close',
'apg:guardrail:display', 'apg:guardrail:redirect',
'apg:guardrail:lock', 'apg:guardrail:end',
'apg:voice:start', 'apg:voice:end',
'apg:dictate:start', 'apg:dictate:end',
'apg:info:open', 'apg:info:close',
'apg:disclaimer:open', 'apg:disclaimer:close',
'apg:footer:cta',
].forEach((name) => window.addEventListener(name, (e) => console.log(name, e.detail)));

Ask the Agent a question. You should see apg:prompt:submit, then apg:completion:start, then apg:completion:finish with a completionId. If those three arrive, your script will see everything else too.

Troubleshooting

  • There is no Scripts tab, or no UI Settings page. The Agent has no web interface turned on. Turn on Standalone Web UI or Embedded Web Widgets under General, then Capabilities, and select Save Changes.
  • The script does not run at all. Check that it is wrapped in <script> tags. The field takes markup, not bare JavaScript, and pasting a snippet without its tags leaves text on the page rather than code that runs.
  • The Agent’s page is broken since you saved. Your code runs exactly as written, so a syntax error in it can stop the rest of the page. Clear the field, save, and reload before looking for the fault elsewhere.
  • No events arrive. Confirm you are on the Agent’s own page rather than your own site around it. On an embedded Agent the events fire inside the iframe, so a listener added by your site’s own tag never sees them.
  • Events arrive but your analytics records nothing. Your listener is probably running before your analytics library has loaded. Load the library first in the same field, and queue events rather than calling into it directly.
  • userId is always empty. Nothing sets it unless your integration does. Identify the visitor by deviceId instead, which every Agent produces on its own.
  • Every identifier is empty. The visitor’s browser is blocking storage, which is what these are kept in. There is no way around it, so count those visits by event rather than by person.
  • A Call to Action is counted twice. Listen for apg:cta:display rather than watching for the element. The event is already limited to once per answer.
  • A guardrail is counted more often than it fired. Ignore apg:guardrail:display where source is hydrate. That is a reload of an existing lockout, not a new one.
  • apg:completion:error never gives you a completionId. It does not carry one. Correlate by session or conversation instead, or read the failure in Chat Logs.

Next Step

Continue to Chat Logs to read the same conversations inside Console, or to Analytics for the counts Console keeps on its own.