Analytics
AI traffic
Sessions that arrived from an AI engine, split by engine, with the pages they landed on and what they did next.

What this is for
Citations record that an AI engine named your
domain as a source for an answer. That measures the answer, not what the
reader who saw it actually did. AI Traffic
reads your Google Analytics 4 property for the sessions that arrived from an
AI engine's own referrer, so you can see whether a citation turned into a
visit, and whether that visit converted
(api/routers/integrations.py#ai_traffic_summary). It is the outcome side of
the same loop
Google rankings versus AI citations
opens on the input side: citations measure whether engines pull you in, AI
Traffic measures whether the people they send you do anything once they land.
What counts as AI traffic
Every session GA4 logs carries a source, not always a referring host: it
reads (direct) when there is none
(api/services/integrations/google/ga4_attribution.py#_upsert_metrics).
AI Traffic keeps only the sessions whose source matches one of 21 known AI
referrer hosts, case-insensitively and regardless of scheme
(https://chatgpt.com and chatgpt.com match the same way), each resolving
to one of ten canonical engines: chatgpt.com, chat.openai.com and
openai.com all resolve to ChatGPT; grok.com, x.ai and grok.x.ai all resolve
to Grok; every other engine carries one to three hosts of its own
(api/services/integrations/google/ga4_referrers.py#classify_referrer,
#_AI_ENGINE_HOSTS). A session whose source is not on that list, including
one with no referrer at all, is not counted here, whatever channel it
actually came from. That classification is the only place in the product
that decides whether a session is AI-referred; nothing else here duplicates
it with a second definition.
Five of those ten engines, ChatGPT, Claude, Gemini, Grok, and Perplexity, are
the engines the rest of the product runs prompts against, and they carry
their usual name and logo here too. The other five, Copilot, You.com, Phind,
Kagi, and Metaphor, are not engines DiscoveredBy queries directly, but GA4
still reports sessions from them, so they appear here with a title-cased name
and a plain colour instead of a logo
(frontend/src/lib/provider-meta.js#PROVIDER_META).
What you see
The hero number is total sessions for the window, with a breakdown by engine
underneath, each row carrying its share of the window's traffic and, where
that engine also had traffic in the prior window to compare against, a
change figure; an engine appearing for the first time shows no change at all
rather than a percentage measured from zero
(api/services/integrations/google/ai_referral_metrics.py#engine_summary,
frontend/src/lib/components/DeltaChip.svelte#delta).
Selecting an engine's row filters the whole screen to that engine, not just
the table beneath it: the hero sessions figure, its prior-window delta, and
the conversion figures all switch to that engine's own numbers, and the
landing-page table narrows to the pages it sent traffic to. The row's own
share still tells you how much of the window's traffic that engine
accounts for
(frontend/src/routes/(app)/ai-traffic/+page.svelte#activeEngine, #sessions).
Selecting the same engine again, or the "Show all engines" button, returns
every figure to the whole-window total. Following a link that names an
engine with no traffic in the current window shows a plain note instead of a
table that just looks empty
(frontend/src/routes/(app)/ai-traffic/+page.svelte#staleFilter).
Landing pages are ranked by sessions, each with the engines that sent it
traffic and, again only where it drew traffic in the prior window, its own
change against that window
(api/services/integrations/google/ai_referral_metrics.py#aggregate_pages).
The list is capped at the top 50 pages by session count; past that point the
page reads "Top 50 pages by sessions" rather than a full count, so a site
with more AI-referred landing pages than that sees the leaders, not
everything (api/routers/integrations.py#AI_TRAFFIC_PAGE_TOP_N,
frontend/src/routes/(app)/ai-traffic/+page.svelte#PAGE_CAP).
Conversions
Whether the screen shows conversion numbers at all is decided once, for the
whole window: if total conversions across every engine are zero, the screen
shows a prompt to name your conversion events instead of a row of zeros,
even filtered down to one engine that happens to have none
(frontend/src/routes/(app)/ai-traffic/+page.svelte#hasConversions). That
keeps the layout in place as you switch engines rather than jumping every
time.
Conversions are not counted at the level you read them. The import asks GA4
for one row per day, page, source, medium, and country, and each of those
rows arrives carrying two figures: GA4's own conversion count for it, the
property-level metric GA4 already flags as a conversion, and GA4's revenue
total for the same row
(api/services/integrations/google/ga4_attribution.py#import_ga4_daily_for_project,
#_upsert_metrics). A second query then replaces both figures together, row
by row, wherever one of your named conversion events fired on it, substituting
the count and the value GA4 recorded against your events there; if you have
not named any, the same replacement runs using GA4's own purchase and
sign_up events instead
(api/routers/integrations.py#update_conversion_events,
api/services/integrations/google/ga4_attribution.py#_load_project_context,
#_fetch_conversion_overrides, #_DEFAULT_CONVERSION_EVENTS). So naming
events does not restrict what counts: it overrides GA4's own figures on the
rows where your events fired, and GA4's own figures stand everywhere else.
Every conversion figure on the screen is built from sums of those underlying
rows: the hero's conversion count, its rate, and the value beside it; each
engine's rate and value; and each landing page's count and value
(api/services/integrations/google/ga4_attribution.py#_rebuild_ai_referral_attribution,
api/services/integrations/google/ai_referral_metrics.py#engine_summary,
#aggregate_pages,
frontend/src/routes/(app)/ai-traffic/+page.svelte#conversionValue). What
you read is therefore a mixture, not one thing or the other: GA4's own count
and revenue across every underlying row where none of the counted events
fired, and those events' own count and value across the rows where they did.
Where the events being counted carry no revenue of their own, a value figure
lands below GA4's revenue for the same sessions by whatever the replaced
rows would otherwise have contributed.
The prior-window comparison for conversions is built for the window's total,
not broken out per engine, so a single engine's row does not carry its own
conversion change chip
(api/services/integrations/google/ai_referral_metrics.py#engine_summary).
The window, and how the two periods compare
Left alone, AI Traffic opens on the last 28 days. You can switch it to 7, 28,
or 90 days; 90 is the ceiling this screen enforces regardless of what you put
in the URL
(frontend/src/routes/(app)/ai-traffic/+page.server.js#MAX_WINDOW_DAYS).
Every change figure on this screen, the hero sessions number, each engine's
row, and each landing page's row, compares the selected window, which runs
from window_days days ago through today, against the window_days days
immediately before that start point: not a fixed prior period or a
year-over-year figure
(api/routers/integrations.py#ai_traffic_summary, #list_ai_traffic).
The home tile reads the same numbers
The dashboard's AI traffic tile and this screen read the exact same
endpoint, computed the exact same way
(api/routers/integrations.py#ai_traffic_summary). The tile reads only
total sessions, total conversions, and the by-engine breakdown; everything
else this screen adds is additional, not a second computation of the same
figures (frontend/src/routes/(app)/+page.svelte#aiTraffic). The one real
difference is the window: the tile is fixed at the last 7 days and does not
follow this screen's window picker, so if the two numbers ever differ, it is
because they describe different windows, not because they disagree about
what counts as a session.
Before you see data
AI Traffic needs Google Analytics 4 connected and a GA4 property mapped to
this project; either without the other leaves the screen showing a setup
prompt instead of a chart
(frontend/src/routes/(app)/ai-traffic/+page.server.js#ga4Connected,
#propertyMapped). The moment a property is mapped, whether that happens
automatically right after you connect or you map one yourself afterward,
its one-time 180-day history import is queued immediately rather than
waiting for the daily timer, so the pull that populates the full 90-day
window this screen allows begins right away
(api/routers/integrations.py#map_ga4_property_to_project,
#_enqueue_initial_import, #_enqueue_ga4_discovery,
api/tasks.py#discover_and_map_ga4_task,
api/services/integrations/google/ga4_attribution.py#import_ga4_daily_for_project,
#_BACKFILL_WINDOW_DAYS). If that immediate enqueue itself fails, the next
daily run backfills any property still missing its history, so a rare
miss costs a short delay, not a lost import. After that first pull, the
same scheduled import that feeds Performance → Traffic also rebuilds this
screen's numbers, once a day
(#_rebuild_ai_referral_attribution); see
Troubleshooting: Performance is empty
for exactly when that recurring import runs.
Related
- Citations: the mention-to-source side this page's traffic is the outcome of
- Google rankings versus AI citations: why a citation and a click are different wins
- Troubleshooting: what an empty or stale-looking screen usually means
Last verified 2026-08-10