Skip to content

Track AI Referral Traffic in Real Time with a Free Cloudflare Worker

Ali Khallad11 min readUpdated
July 1, 2026 , 11 min read
Diagram of the AI referral flow: an AI answer cites you, a visitor clicks through, your Cloudflare Worker reads it at the edge, and you get a push alert
Share

A visitor an AI actively pointed at you is rare and high value: a person who read an answer in ChatGPT, Perplexity, or Gemini, saw your brand named, and clicked through. Most teams find out days later, if at all, buried inside a GA4 report. This guide sets up something better: a free Cloudflare Worker that sits at the edge of your own site and sends a push notification to your phone the moment an AI assistant sends you a real visitor. It can also alert on AI crawlers fetching your pages. No tracking script, no database, no change to your pages, and nothing to slow your site down.

We built and open-sourced the Worker, so the whole thing is on GitHub under an MIT license. This is the setup we wish existed when we started measuring AI traffic, written up end to end.

Why the usual GA4 method only sees half of it

The common advice is to build a custom channel group in GA4 with a regex on the source, matching chatgpt.com, perplexity.ai, and the rest. That is worth doing for reporting, and if you want the GA4 side we wrote it up separately in tracking AI traffic in GA4. But the regex approach has three structural blind spots, and they are the visits you most want to see.

  • Mobile app clicks disappear. The ChatGPT and Perplexity mobile apps frequently strip the referer and never reach GA4 as an AI source. They land in Direct instead. A regex on the referrer host cannot catch what the app never sent.
  • It is not real time. GA4 tells you what happened yesterday. It does not tap you on the shoulder the instant a real person arrives from an AI answer, which is exactly when the signal is most interesting.
  • It says nothing about crawlers. The AI crawl is a separate signal from the human click, and GA4, which runs in the browser, never sees a server-side bot fetch at all.

A Cloudflare Worker runs at the edge, in front of your origin, and inspects the raw request before your page ever loads. That is a different vantage point from a browser tag, and it closes all three gaps. It reads the referer, the native-app referer that mobile AI apps send, and the utm_source on the landing URL, so an app click that stamps utm_source=chatgpt.com with no referer still gets caught. It runs with no cookie and no consent prompt, so ad blockers and consent banners do not touch it.

What the Worker actually watches

AI traffic is really two separate signals, and the Worker treats them as two.

  • Referrals are humans an AI sent you: someone clicked a link in a ChatGPT, Perplexity, Gemini, Claude, Copilot, or Grok answer and landed on your page. Rare and high intent. This is the default.
  • Crawlers are AI bots fetching your pages to train on them, index them, or answer a user live (GPTBot, ClaudeBot, PerplexityBot, and the rest). Far higher volume on a busy site, so they are off by default and throttled when on.

One setting, ALERT_ON, chooses which fire: referrals, crawlers, or both. Detection and the notification run off the response path, so the request is passed straight through to your origin and your site is never slowed down.

Before you start

You need one thing: a site already on Cloudflare, with its traffic proxied (the orange cloud in your DNS). If your DNS record is grey-clouded, the Worker never sees the request. Everything below is on Cloudflare’s free plan.

Step 1: Pick where the alerts go

The Worker can send to several places at once, and it needs at least one. The zero-account option is ntfy: install the ntfy app on your phone, tap Subscribe to topic, and enter a long, hard-to-guess name. Treat that topic like a password, because anyone who knows it can read your alerts. Something like ai-alerts-9f3k2p-yourdomain works. That topic name is the only value you need.

Prefer a team channel? The Worker also supports Telegram, WhatsApp, Discord, Pushover, and a generic webhook for Slack, Zapier, or your own API. Set as many as you like; it sends to all of them.

ChannelWhat to set
ntfy (no account)NTFY_TOPIC
TelegramTELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
DiscordDISCORD_WEBHOOK_URL
PushoverPUSHOVER_TOKEN, PUSHOVER_USER
Slack, Zapier, your APIGENERIC_WEBHOOK_URL

Step 2: Deploy the Worker from GitHub

This is the no-terminal path. First, fork the repo into your own GitHub account: open the repository and click Fork. Cloudflare deploys from your fork and redeploys automatically whenever you push a change to it. It does not pull in later updates to the tool on its own; when you want those, use GitHub’s Sync fork button and it will deploy the new version. Forking first also sidesteps the permission error Cloudflare can throw when it tries to create a repo copy for you.

In the Cloudflare dashboard, open Workers & Pages and click Create application. On the Ship something new screen, choose Continue with GitHub, and authorize the Cloudflare GitHub app to access your fork the first time it asks.

Cloudflare Workers create screen titled Ship something new with Continue with GitHub as the first option

Pick your GitHub account, then select your fork, ai-traffic-alerts-for-cloudflare, from the list and click Next.

Cloudflare Select a repository screen with your forked ai-traffic-alerts-for-cloudflare repository listed under your GitHub account

Cloudflare reads the project’s wrangler.toml, so the deploy command and defaults are already filled in and ALERT_ON starts on referrals. Leave everything as it is and click Deploy.

Cloudflare Set up your application screen showing the project name ai-traffic-alerts-for-cloudflare, the deploy command npx wrangler deploy, and a Deploy button

In under a minute the Worker builds from your fork and goes live on its own workers.dev URL, and every future push to your fork redeploys it automatically.

Cloudflare Worker overview showing ai-traffic-alerts-for-cloudflare deployed on its workers.dev URL with a recent version from the master branch

It deploys, but it stays silent until you give it a notification channel and put it in front of your site, which are the next two steps. Prefer the command line? You can skip the dashboard entirely with git clone, npm install, and npx wrangler deploy; the configuration afterward is identical.

Step 3: Add your notification channel

Open the Worker’s Settings, find Variables and secrets, and click Add. Set the Type to Secret, name it NTFY_TOPIC, and paste the topic you chose in Step 1. Add any channel token the same way, as a Secret: a Discord or Slack webhook URL, a Telegram bot token, and so on.

Use the Secret type, not a plaintext Variable. Because you deployed from GitHub, your Worker rebuilds from its wrangler.toml on every push, and a plaintext variable added only in the dashboard is wiped on the next build, so the Worker would run but never send an alert. A Secret is stored separately, survives every build, and is applied at runtime. It also keeps your topic private, which you want anyway, since anyone who knows an ntfy topic can read its alerts.

Cloudflare Worker Variables and secrets panel: ALERT_ON and CRAWLER_THROTTLE_SECONDS as Plaintext, and NTFY_TOPIC as a Secret showing Value encrypted

ALERT_ON already comes from the repo’s wrangler.toml and defaults to referrals, so it is set for you. To turn on the noisier crawler signal permanently, change ALERT_ON to both in your fork’s wrangler.toml rather than the dashboard, for the same reason: settings live in the file, secrets live in the dashboard. Without a channel the Worker runs but stays quiet and logs that no notification channel is configured, which is the first thing to check if a test does not reach your phone.

Step 4: Put it in front of your site

A Worker with no route never runs. In the Worker, open Domains & Routes, click Add, and choose Route (not Custom domain). Enter the pattern yourdomain.com/* and select your zone. A route runs the Worker on your existing site and passes traffic straight through to it; a custom domain would point the hostname at the Worker instead, which is not what you want here.

Cloudflare Add route dialog with the route pattern yourdomain.com/* entered, and Route selected, showing Back, Cancel, and Add route buttons

If you turned on the crawler signal with ALERT_ON=crawlers or both, create one KV namespace named RADAR_KV under Storage & Databases and bind it in the Worker’s settings. It dedupes crawler alerts to one per vendor per purpose per hour so a busy site does not flood you. In plain referrals mode you can skip this entirely; it is never used.

Step 5: Prove it works

Two ways to see your first alert. The natural one: open ChatGPT and ask it about your brand, or ask it to look up your site, the way a real prospect would. When the answer names you and shows a link, click through to your site. Within a few seconds your phone should buzz with an alert that a visitor arrived from ChatGPT and the page they landed on. That is the whole thing working end to end, the exact path a customer takes.

The quick one, if AI is not citing you yet or you just want to confirm the wiring: open this in a browser, using your real domain.

https://yourdomain.com/?utm_source=chatgpt.com

That stamps the visit as coming from ChatGPT, exactly like a real app click that dropped its referer, and the alert fires the same way. To test the crawler signal (only when ALERT_ON is crawlers or both), send a request with a bot user-agent:

curl -A "GPTBot" https://yourdomain.com/

The payload the Worker sends carries a ready-to-read line plus structured fields, so it works in a phone notification and in Slack or your own API without any adapter:

{ "text": "[ai-traffic-alerts] A person arrived from ChatGPT and landed on yourdomain.com/pricing.",
  "type": "referral", "vendor": "ChatGPT", "referrer_host": "chatgpt.com",
  "url": "https://yourdomain.com/pricing", "site": "yourdomain.com", "country": "US" }

If nothing arrives, check two things in order: that the Route shows yourdomain.com/* on the right zone and your site is orange-clouded, and then the Worker’s own logs, which name the exact failure (no channel set, or a channel returning an auth error with its status code).

Step 5: Prove it works

Two ways to see your first alert. The natural one: open ChatGPT and ask it about your brand, or ask it to look up your site, the way a real prospect would. When the answer names you and shows a link, click through to your site. Within a few seconds your phone should buzz with an alert that a visitor arrived from ChatGPT and the page they landed on. That is the whole thing working end to end, the exact path a customer takes.

The quick one, if AI is not citing you yet or you just want to confirm the wiring: open this in a browser, using your real domain.

https://yourdomain.com/?utm_source=chatgpt.com

That stamps the visit as coming from ChatGPT, exactly like a real app click that dropped its referer, and the alert fires the same way. To test the crawler signal (only when ALERT_ON is crawlers or both), send a request with a bot user-agent:

curl -A "GPTBot" https://yourdomain.com/

The payload the Worker sends carries a ready-to-read line plus structured fields, so it works in a phone notification and in Slack or your own API without any adapter:

{ "text": "[ai-traffic-alerts] A person arrived from ChatGPT and landed on yourdomain.com/pricing.",
  "type": "referral", "vendor": "ChatGPT", "referrer_host": "chatgpt.com",
  "url": "https://yourdomain.com/pricing", "site": "yourdomain.com", "country": "US" }

If nothing arrives, check two things in order: that the Route shows yourdomain.com/* on the right zone and your site is orange-clouded, and then the Worker’s own logs, which name the exact failure (no channel set, or a channel returning an auth error with its status code).

What this can and cannot tell you

Honesty first, because an edge tool has real limits and it is better to know them going in. It tells you that a human landed from an AI assistant and on which page, that an AI crawler hit you and which vendor and purpose, and the visitor’s country, all in real time. A few things it cannot see, by design of how the platforms work:

  • Gemini crawls. You see the humans Gemini sends you, but Gemini answers from Google’s existing index and has no live-fetch bot to catch, so you will not see it crawl.
  • Copilot and Grok fetches. Their provider fetch shows up as a plain browser user-agent with no bot token, so there is no crawler signature to match. Their human clickthroughs are caught whenever the click carries their referer or a utm_source.
  • Google AI Overviews and AI Mode referrals. These arrive as plain google.com and are indistinguishable from an ordinary Google click, so flagging them would mislabel most of your Google traffic. They are left alone on purpose.

Whether a human click carries a referer at all is decided by the sending platform’s referrer policy, not the browser, so a share of AI clickthroughs is simply unrecoverable at the edge. That is the honest ceiling of any log-based approach, including GA4.

Where this fits, and where it stops

This Worker answers one question well and for free: which AI engines actually touch your site, and which ones send you people, the moment it happens. It is the fastest first look at AI traffic you can set up, and it catches the mobile-app clicks a browser tag misses.

What it cannot answer are the questions one level up: which prompts you show up for and which you are missing, which exact sources the assistants cite when they describe your category, whether an AI recommended a competitor instead of you, and whether any of that visibility turned into revenue. Those need measurement across the assistants, not just your own edge logs, and the engines cite very different sources from each other, so a single view of your logs cannot stand in for it. That cross-assistant measurement is the problem we built SurfacedBy to solve, and this Worker is the free front door to it. Deploy it, watch your first AI visitor arrive, and you will quickly want to know why.