How to Detect Cloudflare Browser Insights on Any Website
Cloudflare Browser Insights is the real user monitoring beacon behind what Cloudflare now sells as Cloudflare Web Analytics. It is a single script, loaded from static.cloudflareinsights.com, that reports page load timing and Core Web Vitals from real visitors' browsers back to Cloudflare. Because it is opt-in and separate from the Cloudflare proxy, "the site is on Cloudflare" and "the site uses Browser Insights" are two different facts, and the second one is the one this guide shows you how to establish.
The article walks through the exact fingerprint the beacon leaves in HTML, a curl-and-grep check for a single page along with the one request header that makes or breaks it, and the DetectZeStack API for answering the question across a domain list. Every JSON example uses the fields the API actually returns, and the worked example is a live response from a real site.
What Cloudflare Browser Insights Is (and How It Differs from Cloudflare CDN)
Cloudflare's core product is a reverse proxy. Traffic for a site enters Cloudflare's edge, gets cached, filtered, and rewritten, and then reaches the origin. That proxy is what the CDN, WAF, and bot management products run on, and it announces itself in response headers on every request it handles.
Browser Insights is a different thing. It is a JavaScript beacon that runs in the visitor's browser, collects Navigation Timing and Web Vitals measurements, and posts them to Cloudflare so the site owner can see real-world performance in the dashboard. Cloudflare launched it under the name Browser Insights in 2019 and later built the free Cloudflare Web Analytics product on the same beacon. The fingerprint databases that detection tools rely on kept the original name, which is why the DetectZeStack API reports it as Cloudflare Browser Insights even on sites whose owners know it only as Web Analytics.
There are two ways the beacon ends up on a page:
- Automatic injection. For a zone proxied through Cloudflare, the owner flips a switch and Cloudflare's edge inserts the script tag into HTML responses on the way out. The origin never sees the tag; it exists only in what the edge delivers.
- Manual snippet. For sites that are not proxied through Cloudflare, the owner pastes the same script tag into their templates, with a site token in the
data-cf-beaconattribute. This is how a site on a completely different host can still show up as using Browser Insights.
The second path is why the CDN and the beacon must be detected independently. A site can have either without the other.
Signals That Reveal Cloudflare Browser Insights on a Page
The beacon leaves exactly two kinds of evidence: a script tag in the HTML, and a global it exposes once it runs. The first is visible to any HTTP client. The second needs a browser.
The beacon.min.js Script Tag from static.cloudflareinsights.com
Here is the tag as delivered on developers.cloudflare.com at the time of writing, with the token left intact because it is public by design:
<script type="module"
src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495"
integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg=="
data-cf-beacon='{"version":"2024.11.0","token":"2bc156e5f250476cb274d269511ffb57","spa":2}'
crossorigin="anonymous"></script>
The parts that matter for detection:
| Signal | Value | What it tells you |
|---|---|---|
| Script host | static.cloudflareinsights.com | The only host the beacon is served from; the anchor for every fingerprint |
| Script path | /beacon.min.js/v<hash> | The build hash after the filename changes with releases; match on beacon.min.js, not on the suffix |
| Beacon config | data-cf-beacon='{...}' | JSON with the site token, beacon version, and an spa flag for single-page-app tracking |
| Integrity hash | integrity="sha512-..." | Subresource Integrity for the current beacon build; present on injected tags, optional on manual ones |
The DetectZeStack fingerprint for Cloudflare Browser Insights matches script sources against the pattern static\.cloudflareinsights\.com/beacon(?:\.min)?\.js. That catches both the minified and unminified filename and ignores whatever versioned suffix Cloudflare appends, so a beacon build update does not break detection.
The data-cf-beacon Attribute and __cfBeaconCustomTag Global
The data-cf-beacon attribute is how the script learns which site it belongs to. Its JSON always carries a token, which is the identifier for the Web Analytics site in Cloudflare's dashboard, and a version string for the beacon release. Manual installs carry the same attribute with the token the owner copied from their dashboard. When spa is present, the beacon also reports client-side route changes rather than only full page loads.
Once the script executes, it also exposes a global named __cfBeaconCustomTag, which the fingerprint database lists as a JavaScript signal for this technology. It is a hook the page can set so the beacon attaches a custom tag to its measurements. Like every runtime global, it only exists inside a browser that has run the page's scripts, so it is useful as a console confirmation but invisible to an HTTP fetch. The DetectZeStack HTTP detector relies on the script tag for that reason.
Why Server Headers Like CF-Ray Do Not Prove Browser Insights
Every response that passes through the Cloudflare proxy carries CF-Ray, usually cf-cache-status, and Server: cloudflare. Those headers are the fingerprint for the Cloudflare CDN entry, and they say nothing about the beacon. Two things follow:
- A site with
CF-Rayand no beacon script uses Cloudflare's proxy but has not enabled Browser Insights. This is the common case; the beacon is off by default. - A site with the beacon script and no
CF-Rayinstalled the manual snippet on a host that is not Cloudflare. The API will report Cloudflare Browser Insights without reporting Cloudflare as the CDN.
If your question is about the proxy layer, the headers and DNS signals are the right evidence, and How to Detect the CDN and Hosting Provider of Any Website covers them. If your question is about analytics, only the script tag counts.
Manual Detection with curl and grep
For a single page, a fetch and a search is enough. The one thing that trips people up is the request header. In our tests, Cloudflare's edge injects the beacon only when the request advertises HTML in its Accept header. A bare curl sends Accept: */*, gets a response without the tag, and leads to a false negative. Send a browser-style Accept header and the tag appears:
curl -sL "https://developers.cloudflare.com/" \
-A "Mozilla/5.0 (compatible; DetectZeStack/1.0)" \
-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
| grep -o 'static.cloudflareinsights.com/beacon[^"]*'
On that site the output is the beacon URL with its build suffix:
static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495
To pull the site token as well, match the attribute instead of the src:
curl -sL "https://developers.cloudflare.com/" \
-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
| grep -o "data-cf-beacon='[^']*'"
data-cf-beacon='{"version":"2024.11.0","token":"2bc156e5f250476cb274d269511ffb57","spa":2}'
Empty output from either command means one of three things: the beacon is not installed, the request did not carry an HTML Accept header, or a bot challenge returned a page other than the real one. Check the HTTP status and the response size before concluding the site is clean. Manual checks are fine for one domain and painful for fifty, which is where the API comes in.
The Accept header gotcha applies to your own scanners too. Any crawler that fetches HTML without a text/html Accept header will systematically undercount Browser Insights on Cloudflare-proxied sites, because the tag is added at the edge per request rather than stored in the origin HTML. The DetectZeStack fetcher sends a browser-style Accept header on every scan, which is why the live response below includes the beacon.
Detect Cloudflare Browser Insights with the DetectZeStack API
The DetectZeStack /analyze endpoint fetches the page over HTTP, runs the full fingerprint set against headers and HTML, adds DNS and TLS signals, and returns every detected technology as JSON. Cloudflare Browser Insights is reported as its own entry in the technologies array, in both the Analytics and RUM categories, alongside the separate Cloudflare CDN entry when the proxy is present.
One-Call Detection with /analyze and the Analytics and RUM Categories
The public /demo endpoint runs the same detector with no authentication. It is rate-limited per IP address, so it is for confirming the response shape rather than for scanning, but it needs nothing beyond curl:
curl -s "https://detectzestack.com/demo?url=https://developers.cloudflare.com" | python3 -m json.tool
The live response for that domain, trimmed to the entries this article cares about, looks like this:
{
"url": "https://developers.cloudflare.com",
"domain": "developers.cloudflare.com",
"technologies": [
{
"name": "Cloudflare",
"categories": ["CDN"],
"confidence": 100,
"description": "Cloudflare is a web-infrastructure and website-security company, providing content-delivery-network services, DDoS mitigation, Internet security, and distributed domain-name-server services.",
"website": "https://www.cloudflare.com",
"icon": "CloudFlare.svg",
"source": "http",
"version": "",
"cpe": ""
},
{
"name": "Cloudflare Bot Management",
"categories": ["Security"],
"confidence": 100,
"description": "Cloudflare bot management solution identifies and mitigates automated traffic to protect websites from bad bots.",
"website": "https://www.cloudflare.com/en-gb/products/bot-management/",
"icon": "CloudFlare.svg",
"source": "http",
"version": "",
"cpe": ""
},
{
"name": "Cloudflare Browser Insights",
"categories": ["Analytics", "RUM"],
"confidence": 100,
"description": "Cloudflare Browser Insights is a tool that measures the performance of websites from the perspective of users.",
"website": "https://www.cloudflare.com",
"icon": "CloudFlare.svg",
"source": "http",
"version": "",
"cpe": ""
},
{
"name": "OneTrust",
"categories": ["Cookie compliance"],
"confidence": 100,
"description": "OneTrust is a cloud-based data privacy management compliance platform.",
"website": "https://www.onetrust.com",
"icon": "OneTrust.svg",
"source": "http",
"version": "",
"cpe": ""
}
],
"categories": {
"Analytics": ["Cloudflare Browser Insights"],
"CDN": ["Cloudflare"],
"Cookie compliance": ["OneTrust"],
"RUM": ["Cloudflare Browser Insights"],
"Security": ["HSTS", "Cloudflare Bot Management"]
},
"meta": { "status_code": 200, "tech_count": 8, "scan_depth": "full" },
"cached": false,
"response_ms": 1448
}
Three things to notice. The three Cloudflare products are three separate entries, each with its own evidence: the CDN from headers, Bot Management from its challenge script, Browser Insights from the beacon. The categories map lists Browser Insights under both Analytics and RUM, so you can filter on either. And source is http, meaning the match came from the fetched HTML rather than from DNS or TLS signals.
For real usage, sign up on RapidAPI and call /analyze with your key. The shape is identical to /demo; the difference is that requests count against your monthly plan instead of the per-IP demo limit. Piping through jq isolates the entry:
curl -s "https://detectzestack.p.rapidapi.com/analyze?url=https://developers.cloudflare.com" \
-H "x-rapidapi-key: $RAPIDAPI_KEY" \
-H "x-rapidapi-host: detectzestack.p.rapidapi.com" \
| jq '.technologies[] | select(.name == "Cloudflare Browser Insights")'
To see the whole analytics layer at once, including Google Analytics, Google Tag Manager, or any other tag that shares the page with the beacon, filter on the category instead of the name:
curl -s "https://detectzestack.p.rapidapi.com/analyze?url=https://www.cloudflare.com" \
-H "x-rapidapi-key: $RAPIDAPI_KEY" \
-H "x-rapidapi-host: detectzestack.p.rapidapi.com" \
| jq '.categories.Analytics'
On cloudflare.com's own homepage that returns both the beacon and Google Analytics:
[
"Cloudflare Browser Insights",
"Google Analytics"
]
Yes/No Checks with /check?tech=Cloudflare Browser Insights
If you only need a boolean for one domain, the /check endpoint does the filtering server-side. The tech parameter is matched case-insensitively against the canonical technology name, so URL-encode the spaces and pass the full name:
curl -s "https://detectzestack.p.rapidapi.com/check?url=developers.cloudflare.com&tech=Cloudflare%20Browser%20Insights" \
-H "x-rapidapi-key: $RAPIDAPI_KEY" \
-H "x-rapidapi-host: detectzestack.p.rapidapi.com"
The response is a flat object:
{
"domain": "developers.cloudflare.com",
"technology": "Cloudflare Browser Insights",
"detected": true,
"confidence": 100,
"version": "",
"categories": ["Analytics", "RUM"],
"response_ms": 1402,
"cached": false
}
When the beacon is absent, detected is false, confidence is 0, and technology echoes the name you asked for. Results are cached per domain for 24 hours, so a repeat check on the same domain returns "cached": true without a fresh fetch.
Scanning a List of Domains with /analyze/batch
For lists, /analyze/batch accepts up to 10 URLs per request and scans them concurrently on the server. Each URL still counts as one request against your plan, so the batch endpoint is about wall-clock time, not quota. The request body is a JSON object with a urls array:
curl -s -X POST "https://detectzestack.p.rapidapi.com/analyze/batch" \
-H "x-rapidapi-key: $RAPIDAPI_KEY" \
-H "x-rapidapi-host: detectzestack.p.rapidapi.com" \
-H "Content-Type: application/json" \
-d '{"urls": ["developers.cloudflare.com", "blog.cloudflare.com", "example.com"]}' \
| jq -r '
.results[]
| select(.result != null)
| "\(.url)\t\(
if any(.result.technologies[]; .name == "Cloudflare Browser Insights")
then "browser-insights" else "-" end
)\t\(
if any(.result.technologies[]; .name == "Cloudflare")
then "cloudflare-cdn" else "-" end
)"
'
The response has a results array with one item per submitted URL. Each item carries either a result object in the same shape as a single /analyze response or an error string, plus top-level successful, failed, and total_ms counters. The jq above prints one line per scanned domain with two columns, so you can see at a glance which sites have the proxy, the beacon, both, or neither:
developers.cloudflare.com browser-insights cloudflare-cdn
blog.cloudflare.com browser-insights cloudflare-cdn
example.com - -
To run a file of a thousand domains, chunk it into tens and feed each chunk to the same call. A short bash loop that writes one JSON line per domain:
#!/usr/bin/env bash
# cf-insights-batch.sh — usage: cf-insights-batch.sh domains.txt > results.jsonl
set -euo pipefail
KEY="${RAPIDAPI_KEY:?set RAPIDAPI_KEY}"
HOST="detectzestack.p.rapidapi.com"
split -l 10 "$1" chunk_
for f in chunk_*; do
body=$(jq -R -s -c 'split("\n") | map(select(length > 0)) | {urls: .}' "$f")
curl -s -X POST "https://${HOST}/analyze/batch" \
-H "x-rapidapi-key: ${KEY}" \
-H "x-rapidapi-host: ${HOST}" \
-H "Content-Type: application/json" \
-d "$body" \
| jq -c '.results[] | {
url: .url,
error: (.error // null),
browser_insights: (any((.result.technologies // [])[]; .name == "Cloudflare Browser Insights")),
analytics: ((.result.categories // {}).Analytics // [])
}'
rm -f "$f"
done
Every output line carries the domain, a browser_insights boolean, the full list of analytics tools found on the page, and a non-null error when the fetch failed. Keep the error rows; a domain that refused the scan is "unknown," not "no beacon," and collapsing the two will skew any adoption numbers you compute. For concurrency, retries, and quota planning across a larger list, see Batch Scan 1,000 Websites for Tech Stack.
Use Cases: Analytics Stack Audits, Privacy Reviews, and Prospecting
Browser Insights is a niche detection on its own, but it is a sharp one, because it is opt-in and because Cloudflare positions it as a privacy-first alternative to cookie-based analytics. The teams who ask for it:
- Analytics and web performance teams auditing what is actually on their pages. The
Analyticscategory in one/analyzeresponse lists every measurement tag together, so a company that believes it standardized on one tool can see where a marketing site still runs two. The same scan across every property in a portfolio is the inventory. - Privacy and compliance reviewers mapping third-party data flows. The beacon sends timing data to Cloudflare, and Cloudflare describes it as cookieless with no client-side state. Whether that meets a given policy is a question for the reviewer, but knowing it is present, and seeing it next to entries like OneTrust in the same response, is the starting point. How to Detect OneTrust on Any Website covers the consent side.
- Cloudflare partners and agencies prospecting for accounts that have adopted more than the free proxy. A domain with the CDN, Bot Management, and Browser Insights all present is a customer who has turned on several products and is a candidate for the rest. How to Detect Cloudflare Bot Management on Any Site covers the security entry that usually sits beside this one.
- Analytics vendors building competitive lists. "Uses Cloudflare Web Analytics and not Google Analytics" is a query the batch script above answers directly from the
analyticsarray, and it identifies sites that have already chosen a lightweight, privacy-oriented tool. How to Detect Google Analytics on Any Website is the companion piece for the other side of that filter.
Conclusion and Next Steps
Detecting Cloudflare Browser Insights comes down to one script tag from static.cloudflareinsights.com, and to remembering that the tag proves the analytics beacon while CF-Ray proves only the proxy. On one page, a curl with a browser-style Accept header and a grep for beacon.min.js settles it. Across a portfolio, the Cloudflare Browser Insights entry in the DetectZeStack technologies array settles it per domain, filed under Analytics and RUM and sitting beside the separate Cloudflare CDN entry so you never have to infer one from the other. For why HTTP-level detection like this sees things browser extensions cannot, and the reverse, see DNS + TLS Detection vs Browser Extensions.
- Confirm the response shape with
curl "https://detectzestack.com/demo?url=https://developers.cloudflare.com". No key required. - Sign up at rapidapi.com/mlugoapx/api/detectzestack and copy your
x-rapidapi-key. The free tier is 100 requests per month with no credit card. - Run the
/checkexample above against a domain you care about, then move to/analyze/batchwhen the list grows past a handful.
Related Reading
- How to Detect Cloudflare Bot Management on Any Site — The Security entry that usually appears beside Browser Insights on Cloudflare-proxied sites
- How to Detect Google Analytics on Any Website — The other tag in the Analytics category; measurement IDs, gtag.js, and bulk scanning
- How to Detect the CDN and Hosting Provider of Any Website — The header and DNS evidence behind the Cloudflare CDN entry, as opposed to the beacon
- How to Detect OneTrust on Any Website — The consent platform that co-occurs with analytics beacons in privacy reviews
- Batch Scan 1,000 Websites for Tech Stack — Concurrency, retries, and quota patterns for /analyze/batch
- DNS + TLS Detection vs Browser Extensions — What HTTP-layer detection sees that extensions miss, and vice versa
Detect Cloudflare Browser Insights and Every Other Analytics Tag in One API Call
One HTTP request returns every analytics beacon, CDN, framework, CMS, and security product on a page, with the Cloudflare proxy and the Cloudflare beacon reported separately. 100 requests per month free. No credit card.
Get your free API key