How to Detect Cloudflare Browser Insights on Any Website

September 4, 2026 · 10 min read

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:

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:

SignalValueWhat it tells you
Script hoststatic.cloudflareinsights.comThe 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 configdata-cf-beacon='{...}'JSON with the site token, beacon version, and an spa flag for single-page-app tracking
Integrity hashintegrity="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:

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:

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.

  1. Confirm the response shape with curl "https://detectzestack.com/demo?url=https://developers.cloudflare.com". No key required.
  2. 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.
  3. Run the /check example above against a domain you care about, then move to /analyze/batch when the list grows past a handful.

Related Reading

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

Get API updates and tech detection tips

Join the mailing list. No spam, unsubscribe anytime.