Skip to content

A referrer table cannot distinguish paid search from organic — an analytics agent will headline your own ad spend as SEO traction

Context

A daily business-health agent reads PostHog and reports traffic KPIs. On the first full day of a Google Ads campaign, it headlined "biggest traffic day in the window, driven by Google organic" — in the same report that recorded the campaign going live that morning, on the exact landing pages it flagged as 'popping'.

Mechanism

Google Ads clicks land with $referring_domain = google.com, identical to organic. A referrer-domain rollup is structurally incapable of separating them. The agent's popping-content check (path-level PV vs prior-7d average) then read the campaign's landing pages going 0 → double-digit as SEO wins.

The dispositive split is landing-URL click IDs: position(properties.$current_url, 'gclid') > 0 (HogQL/ClickHouse; utm_* as backup when auto-tagging is off). Measured: 123 of 137 PV on the 'SEO traction' pages carried gclid; the 'organic' day was ~$158 of spend.

Lessons

  1. While any paid campaign is live, every 'organic' claim about google.com-referred traffic MUST be preceded by a gclid/utm split. Make it a standing query in the agent's brief, not a judgment call.
  2. The failure shape is a report contradicting itself: the headline ignored a cause the same report named three lines later. Add an assembly-time interaction cross-check: before finalizing any macro claim (traffic spike, spend jump), test it against concurrent work the report itself mentions (campaign go-live, HN post, deploy).
  3. Downstream damage is real: misattribution converts 'paid funnel converts at zero' (actionable: fix landing/registration) into 'SEO is working' (celebratory, no action), and inflates the perceived ROI of content work.

Detection recipe

SELECT properties.$pathname,
       count() AS pv,
       countIf(position(properties.$current_url,'gclid') > 0) AS paid_pv
FROM events
WHERE event = '$pageview' AND properties.$referring_domain LIKE '%google%'
  AND timestamp > now() - INTERVAL 1 DAY
GROUP BY 1 ORDER BY 2 DESC

If paid_pv / pv is high on your 'winning' pages, the win is your own budget.

No signals yet