Telltale Bot

Install guide

Send the verdict to your analytics

Once our tag is on your pages, each pageview gets sent a verdict event: a person, an AI agent acting for a person, or a bot. How you use those verdicts is up to you, but the following is a guide on how to consume them. These recipes are not guaranteed and are subject to change as the analytics platforms themselves change.

Note that while Telltale Bot is designed to send verdicts as quickly as possible, there will also be some page loads that won't receive a verdict, meaning your own analytics platform will never report everything that our own reporting platform does.

Install the tag first (Install Telltale Bot). The recipes below read the verdict from the page events or, in a Tag Manager, from the dataLayer.

How it works, whatever the tool

Every recipe below has this shape, with the tool's own call inside send:

<script>
(function () {
  var sent = false;
  function send(v) {
    if (sent || !v) return;
    sent = true;
    // the tool's own call goes here, with v.actor, v.agentId, ...
  }
  addEventListener('telltale:verdict', function (e) { send(e.detail); });
  if (window.Telltale && Telltale.verdict) send(Telltale.verdict);
})();
</script>

Google Analytics 4

With our recipe each pageview sends GA4 one extra event, telltale_verdict, with these five parameters:

ParameterWhat it holds
telltale_actorhuman, agentic or bot
telltale_agent_idWho it is, such as claude-extension or googlebot; human for people
telltale_confidencehigh, medium or low
telltale_declaredtrue when the visitor named itself, else false; empty ("(not set)") for people
telltale_eventtelltale.verdict for the first verdict, telltale.verdict-update when it changed after the visitor acted (most pages have none)

It takes three steps: send the event, register its parameters in GA4 as custom dimensions (or you can't report on them!), and check that it arrives.

Step 1: send the event

Easiest, with Google Tag Manager: import our container. Installation in the viewer offers a GTM container with everything below already set up, our tag included (see import a container). You only fill in your GA4 measurement ID. It needs your GA4 Google tag in the same container.

Or set it up by hand in Google Tag Manager. Name everything as below, since the code refers to these names:

  1. Create four Data Layer Variables: Telltale Bot - actor for telltale.actor, Telltale Bot - agentId for telltale.agentId, Telltale Bot - confidence for telltale.confidence and Telltale Bot - declared for telltale.declared.
  2. Create a Constant variable, Telltale Bot - GA4 measurement ID, with your GA4 measurement ID (it starts with G-). If you already have a variable for it, use yours in the code instead.
  3. Turn on GTM's built-in Event variable (Variables, then Configure), if it is off.
  4. Create a Custom Event trigger with the event name ^telltale\.verdict(-update)?$ and Use regex matching ticked.
  5. Create a Custom HTML tag, Telltale Bot - GA4 verdict event, with the code below, on that trigger.
<script>
// Telltale Bot - GA4 verdict event: https://telltale.bot/analytics.html#ga4
// Sends each verdict to GA4 as a telltale_verdict event, through your GA4 Google
// tag in this container, at once rather than with gtag.js's next batch.
// It hands the event to gtag.js the way GTM's own GA4 tags do, after the GTM event
// that fired this tag (so after your consent defaults and the Google tag's setup),
// with a flag gtag.js reads as "send now". Google does not document that path: if
// it is missing, this sends a normal gtag event instead.
(function () {
  try {
    var name = {{Event}}, dl = window.dataLayer || [], eventId, i, m;
    for (i = dl.length - 1; i >= 0; i--) {
      m = dl[i];
      if (m && m.event === name && typeof m['gtm.uniqueEventId'] === 'number') { eventId = m['gtm.uniqueEventId']; break; }
    }
    var params = {
      send_to: {{Telltale Bot - GA4 measurement ID}},
      telltale_actor: {{Telltale Bot - actor}},
      telltale_agent_id: {{Telltale Bot - agentId}},
      telltale_confidence: {{Telltale Bot - confidence}},
      telltale_declared: {{Telltale Bot - declared}},
      telltale_event: name
    };
    var args = (function () { return arguments; })('event', 'telltale_verdict', params);
    var gtm = window.google_tag_manager, mb = gtm && gtm.mb;
    if (eventId && mb && typeof mb.enqueue === 'function') {
      mb.enqueue(args, eventId, { eventMetadata: { is_sgtm_service_worker: true } });
    } else {
      (window.dataLayer = window.dataLayer || []).push(args);
    }
  } catch (e) {}
})();
</script>

Why a Custom HTML tag and not a GA4 Event tag: a GA4 Event tag's event waits about 5 seconds for gtag.js's next batch, and a bot that closes the page first never sends it, so GA4 would undercount bots. This tag sends the event at once, through your Google tag, the way GTM's own GA4 tags do. Google does not document that way; if it ever changes, the tag sends a normal GA4 event instead (batched), so nothing breaks.

Or, without GTM, directly in the page with gtag.js. Add this after your gtag.js snippet. It sends the same event the same way, as long as the tag pushes the verdict to your dataLayer (on unless you turned it off); otherwise it goes with gtag.js's next batch.

<script>
(function () {
  var sent = false;
  function send(v, name) {
    if (!v || typeof window.gtag !== 'function') return;
    var params = {
      telltale_actor: v.actor,
      telltale_agent_id: v.agentId,
      telltale_confidence: v.confidence,
      telltale_declared: v.declared === undefined ? undefined : String(v.declared),
      telltale_event: name
    };
    // Send it at once, not with gtag.js's next batch (about 5 seconds later), the way
    // GTM's own GA4 tags hand gtag.js an event: once gtag.js has taken the tag's
    // dataLayer message. Google does not document this: if it is missing, this sends a
    // normal gtag event instead.
    setTimeout(function () {
      (window.dataLayer = window.dataLayer || []).push(function () {
        try {
          var dl = window.dataLayer, mb = window.google_tag_manager && google_tag_manager.mb, id, i, m;
          for (i = dl.length - 1; i >= 0; i--) {
            m = dl[i];
            if (m && m.event === name && typeof m['gtm.uniqueEventId'] === 'number') { id = m['gtm.uniqueEventId']; break; }
          }
          if (id && mb && typeof mb.enqueue === 'function') {
            mb.enqueue((function () { return arguments; })('event', 'telltale_verdict', params), id, { eventMetadata: { is_sgtm_service_worker: true } });
            return;
          }
        } catch (e) {}
        gtag('event', 'telltale_verdict', params);
      });
    }, 0);
  }
  function first(v) { if (!sent && v) { sent = true; send(v, 'telltale.verdict'); } }
  addEventListener('telltale:verdict', function (e) { first(e.detail); });
  addEventListener('telltale:verdict-update', function (e) { send(e.detail, 'telltale.verdict-update'); });
  if (window.Telltale && Telltale.verdict) first(Telltale.verdict);
})();
</script>

Extra fields. If you chose Data layer fields, add a line for each next to the others: in the gtag.js code, such as telltale_isp_name: v.ispName; in GTM, a Data Layer Variable for it and a line such as telltale_isp_name: {{Telltale Bot - ispName}}. Register each one in step 2 too.

Step 2: register the parameters in GA4

GA4 keeps a parameter for reports only once it is registered. In GA4, go to Admin, then Custom definitions, and click Create custom dimension once for each of the five parameters: set Scope to Event and Event parameter to the parameter's name exactly as in the table above (you can give it the same name as its dimension name).

Use Event scope, never User: when an AI agent works in a person's own browser, GA4 sees that person's user, who would then flip between human and agentic.

Step 3: check it arrives

In GA4's reports

Bots and agents as segments

Because the verdict is event-scoped, filtering a report on telltale_actor keeps only the verdict events, not the pageviews and other events of those visitors. To see everything automated visitors did, use a segment in an exploration (Explore, then Segments):

Plausible

We run this one on our own sites. Add it after Plausible's snippet:

<script>
(function () {
  window.plausible = window.plausible || function () { (plausible.q = plausible.q || []).push(arguments); };
  var sent = false;
  function send(v) {
    if (sent || !v) return;
    sent = true;
    plausible('Telltale Verdict', {
      props: { bot: v.actor, bot_agent: v.agentId },
      interactive: false
    });
  }
  addEventListener('telltale:verdict', function (e) { send(e.detail); });
  if (window.Telltale && Telltale.verdict) send(Telltale.verdict);
})();
</script>

Matomo

Matomo records the verdict as an event (category Telltale Bot, action Verdict, name = the actor), and, if you set them up, in two custom dimensions you can use across Matomo's reports. Add it after Matomo's tracking code:

<script>
(function () {
  var _paq = window._paq = window._paq || [];
  var sent = false;
  function send(v) {
    if (sent || !v) return;
    sent = true;
    // Replace 1 and 2 with the IDs of your two action-scoped custom dimensions.
    _paq.push(['trackEvent', 'Telltale Bot', 'Verdict', v.actor, undefined,
      { dimension1: v.actor, dimension2: v.agentId }]);
  }
  addEventListener('telltale:verdict', function (e) { send(e.detail); });
  if (window.Telltale && Telltale.verdict) send(Telltale.verdict);
})();
</script>

Piano Analytics

Piano Analytics records the verdict as its own event, telltale.verdict, with three event properties: telltale_actor, telltale_agent_id and telltale_confidence. Add it after Piano's tag (the JavaScript SDK, global pa):

<script>
(function () {
  var sent = false;
  function send(v) {
    if (sent || !v || !window.pa || typeof pa.sendEvent !== 'function') return;
    sent = true;
    pa.sendEvent('telltale.verdict', {
      telltale_actor: v.actor,
      telltale_agent_id: v.agentId,
      telltale_confidence: v.confidence
    });
  }
  addEventListener('telltale:verdict', function (e) { send(e.detail); });
  if (window.Telltale && Telltale.verdict) send(Telltale.verdict);
})();
</script>

PostHog, Mixpanel, Amplitude and Segment

Product analytics tools take an event with properties. Each snippet goes after the tool's own snippet, which must be loaded under its usual global name (posthog, mixpanel, amplitude, analytics). Use event properties, not person, user or super properties (see per event, never per user).

PostHog

<script>
(function () {
  var sent = false;
  function send(v) {
    if (sent || !v || !window.posthog) return;
    sent = true;
    posthog.capture('Telltale Verdict', {
      actor: v.actor,
      agent_id: v.agentId,
      confidence: v.confidence
    });
  }
  addEventListener('telltale:verdict', function (e) { send(e.detail); });
  if (window.Telltale && Telltale.verdict) send(Telltale.verdict);
})();
</script>

Then filter or break down any insight by the Telltale Verdict event's actor or agent_id.

Mixpanel

<script>
(function () {
  var sent = false;
  function send(v) {
    if (sent || !v || !window.mixpanel) return;
    sent = true;
    mixpanel.track('Telltale Verdict', {
      actor: v.actor,
      agent_id: v.agentId,
      confidence: v.confidence
    });
  }
  addEventListener('telltale:verdict', function (e) { send(e.detail); });
  if (window.Telltale && Telltale.verdict) send(Telltale.verdict);
})();
</script>

Amplitude

<script>
(function () {
  var sent = false;
  function send(v) {
    if (sent || !v || !window.amplitude) return;
    sent = true;
    amplitude.track('Telltale Verdict', {
      actor: v.actor,
      agent_id: v.agentId,
      confidence: v.confidence
    });
  }
  addEventListener('telltale:verdict', function (e) { send(e.detail); });
  if (window.Telltale && Telltale.verdict) send(Telltale.verdict);
})();
</script>

Amplitude Browser SDK 2. With the older SDK, the call is amplitude.getInstance().logEvent(…).

Segment

<script>
(function () {
  var sent = false;
  function send(v) {
    if (sent || !v || !window.analytics) return;
    sent = true;
    analytics.track('Telltale Verdict', {
      actor: v.actor,
      agent_id: v.agentId,
      confidence: v.confidence
    });
  }
  addEventListener('telltale:verdict', function (e) { send(e.detail); });
  if (window.Telltale && Telltale.verdict) send(Telltale.verdict);
})();
</script>

Segment passes the event on to every destination you have connected, so this one recipe reaches them all.

Any other tool, and other tag managers

The GA4 recipes have run with Google's own gtag.js in our tests, and the GTM one on customer sites; the Plausible one runs on our own sites. The others follow each tool's documentation. If one doesn't work for you, or you would like a recipe for a tool not listed, write to support@telltale.bot.