The wall in front of you is not generic. It is DataDome, or Cloudflare, or Akamai, and each one fails you in its own way, at its own cost, and gives way to its own technique. Before you write a single line of bypass code, spend five minutes on reconnaissance. The loader script URL, the cookies, the response headers and the JS probe profile will usually tell you exactly who you are up against. This is a 2026-07 snapshot; signatures drift, so treat every match as a hypothesis you confirm with a second signal.
Why identification comes first
The single most useful fact about a target is which vendor protects it, because the vendor sets both the difficulty ceiling and the viable approach. “This is Cloudflare Free” and “this is DataDome” describe two different problems, not two points on one scale. Cloudflare Free often clears with a decent TLS stack and a clean header order. DataDome builds a behavioral profile from the first request and binds its cookie to your IP, so the same trick that beats Cloudflare gets you nowhere. Misidentify the wall and you burn proxies solving the wrong problem.
The ecosystem already treats this as a first-class step. is-antibot from microlink classifies 20-plus vendors from client-observable artifacts; Scrapfly ships an Antibot-Detector; the Proxies.sx Bot Detection Scanner covers 14 vendors and attaches a bypass-difficulty rating; Blackwall/BotGuard offers a scan endpoint. All of them lean on the same three passive signals (script URL, cookie, header) plus, increasingly, the shape of the JS probe itself. None of it requires you to trip the challenge. It is pure reconnaissance you can do with one navigation and a network log.
The four signals
Passive identification draws on four observables, in roughly descending order of how easy they are to spoof away:
- Loader script URL: the hostname and path of the detection agent. Historically the strongest single tell, now the least reliable as first parties proxy and randomize it.
- Cookies. Names are highly stable because they are load-bearing for the vendor’s own server-side logic.
datadome,cf_clearance,_abck,reese84are near-unforgeable tells. - Response headers.
x-datadome,CF-RAY,x-kpsdk-*leak the vendor at the edge before any JS runs. - JS probe profile: the distribution of Web API calls the agent makes. Even when the URL is randomized, the behavior has a fingerprint. DataDome hammers
getClientRects, Castle.io hammersmeasureText, reCAPTCHA barely touches the graphics stack at all.
The fourth signal is the one that survives obfuscation, and it is where a consistency scanner earns its keep. The counts below come from engine-level API-access recon run in 2026-07 against each vendor’s live demo: one fresh browser session per site, all Web IDL access logged, attributed to the triggering script URL.
Vendor signature table
| Vendor | Loader script URL (often first-party-proxied) | Cookies | Response headers | JS probe profile (observed) |
|---|---|---|---|---|
| DataDome | js.datadome.co/tags.js |
datadome (bound to IP) |
x-datadome, x-datadome-* |
Huge DOMRect: getClientRects + getBoundingClientRect ~1900 combined; offsetWidth font measurement; plugins + UA-CH; WebGL getParameter x100 / getExtension x37; audio; FontFaceSet |
| Cloudflare (Turnstile / BM) | challenges.cloudflare.com/turnstile/v0/*/api.js + .../challenge-platform/* |
cf_clearance, __cf_bm |
CF-RAY |
WebGL renderbuffer / getInternalformatParameter actually exercised; WebGPU adapter.info; OffscreenCanvas getImageData in a worker; FontFace.load x55; OfflineAudioContext; WebRTC. Also classifies at TLS (JA3/JA4). |
| reCAPTCHA v2/Enterprise | gstatic.com/recaptcha/releases/*/recaptcha__en.js + recaptcha/api2/webworker.js |
_GRECAPTCHA + Google-domain cookies |
, | Light: navigator (incl. WorkerNavigator.userAgent x26), battery, storage, mediaDevices. Weight is behavior / Google cookie / token / action. |
| Arkose / FunCaptcha | client-api.arkoselabs.com/v2//api.js |
arkose / fc token |
, | Landing loader only reads navigator.connection; real puzzle is in a post-trigger enforcement iframe. |
| FingerprintJS Pro | first-party-proxied <host>/<random>/web/v4/<random> (e.g. fingerprint.com/r4a0Rd2Xs/web/v4/…) |
_iidt |
, | Full-spectrum: WebGL getParameter x96 / getExtension x35; WebGPU limits x71; font measurement; canvas; audio; WebRTC; battery; permissions; StorageManager.getDirectory (OPFS); WorkerNavigator. |
| Castle.io | @castleio/castle-js/dist/castle.browser.js |
, | , | Canvas measureText x337 / fillText x118; OffscreenCanvas getImageData x64; WebGPU; WebGL. |
| Akamai | no comparable public loader | _abck (stateful; ~-1~ = not cleared), ak_bmsc |
, | ~512KB sensor_data POST; identify by cookie state, not JS-probe shape. |
| Kasada | , | , | x-kpsdk-cd, x-kpsdk-ct |
Bare HTTP 429 with no challenge body. |
| Imperva / Incapsula | , | reese84 token, incap_ses |
, | Morphing encode function; needs a real browser to execute. |
A few of these deserve emphasis. Akamai has no loader URL you can grep for the way DataDome does. You identify it by the stateful _abck cookie (the ~-1~ marker means the cookie has not been “cleared” by a valid sensor payload) alongside ak_bmsc and that characteristic half-megabyte sensor_data POST. Kasada announces itself by refusing to say anything: a naked 429 with x-kpsdk-cd / x-kpsdk-ct headers and no challenge page. reCAPTCHA is the odd one out on the JS axis. Its probe profile is deliberately thin, because the detection weight lives in behavior, the Google cookie, the token and the declared action rather than in a graphics-stack sweep.
The URL signal is decaying
The loader URL used to be enough. It no longer is, and FingerprintJS Pro is the clearest illustration of why. The agent is served from a randomized path under the site’s own domain (fingerprint.com/r4a0Rd2Xs/web/v4/…, demo.fingerprint.com/DBqbMN7…/web/v4/…) specifically to defeat URL blocklists.
The agent sits at a random first-party path like
/r4a0Rd2Xs/web/v4/…precisely so a URL blocklist can’t catch it. You have to fingerprint the behavior; the hostname won’t tell you anything. (the FingerprintJS Pro deployment pattern observed in recon)
This is the direction of travel for the whole category. As more vendors offer first-party proxying, hostname matching degrades from a primary signal to a corroborating one. The durable tell becomes the JS probe profile: FingerprintJS Pro’s full-spectrum sweep (WebGL getParameter x96, WebGPU limits x71, OPFS via StorageManager.getDirectory, a WorkerNavigator read) looks nothing like DataDome’s DOMRect obsession or reCAPTCHA’s near-silence, even when both are served from a random path on the target’s own domain. Classify on what the script does; its origin is beside the point.
A signature matcher
Never rely on one signal. Score each vendor across all four observables and require agreement. Here is a compact matcher that takes the loaded script URLs, the cookie names and the response headers and returns ranked candidates.
// Passive anti-bot classifier. Combine signals; never trust one alone.
const SIGNATURES = [
{ vendor: "DataDome",
url: [/js\.datadome\.co\/tags\.js/],
cookie: [/^datadome$/],
header: [/^x-datadome/i] },
{ vendor: "Cloudflare",
url: [/challenges\.cloudflare\.com\/turnstile\/v0\//,
/challenges\.cloudflare\.com\/cdn-cgi\/challenge-platform\//],
cookie: [/^cf_clearance$/, /^__cf_bm$/],
header: [/^cf-ray$/i] },
{ vendor: "reCAPTCHA",
url: [/gstatic\.com\/recaptcha\/releases\//, /recaptcha\/api2\/webworker\.js/],
cookie: [/^_GRECAPTCHA$/],
header: [] },
{ vendor: "FingerprintJS Pro",
// hostname is unreliable (first-party proxied); path shape is the tell
url: [/\/[A-Za-z0-9]{6,}\/web\/v4\//],
cookie: [/^_iidt$/],
header: [] },
{ vendor: "Akamai",
url: [],
cookie: [/^_abck$/, /^ak_bmsc$/],
header: [] },
{ vendor: "Kasada",
url: [],
cookie: [],
header: [/^x-kpsdk-(cd|ct)$/i] },
{ vendor: "Imperva",
url: [],
cookie: [/^reese84$/, /^incap_ses/],
header: [] },
];
function classify({ scriptUrls = [], cookieNames = [], headerNames = [] }) {
const anyMatch = (patterns, values) =>
patterns.some((re) => values.some((v) => re.test(v)));
return SIGNATURES
.map((sig) => {
const hits = [];
if (anyMatch(sig.url, scriptUrls)) hits.push("url");
if (anyMatch(sig.cookie, cookieNames)) hits.push("cookie");
if (anyMatch(sig.header, headerNames)) hits.push("header");
// cookie/header are load-bearing for the vendor => weight them higher
const score = hits.reduce(
(s, k) => s + (k === "url" ? 1 : 2), 0);
return { vendor: sig.vendor, hits, score };
})
.filter((r) => r.score > 0)
.sort((a, b) => b.score - a.score);
}
// e.g. cookie `datadome` + header `x-datadome` => score 4, url absent but
// two independent load-bearing signals agree => high confidence.
Two design points. Cookies and headers are weighted above URLs, because a vendor cannot rename datadome or cf_clearance without breaking its own server-side validation, whereas it can trivially move a script. And the _abck and x-kpsdk-* entries carry no URL pattern at all. Akamai and Kasada are cookie/header-only identifications by nature, and pretending otherwise would produce false negatives.
To feed this matcher you need the observables. scriptUrls comes from a network log (CDP Network.responseReceived, an HAR or a proxy); cookieNames from document.cookie plus Set-Cookie on the responses; headerNames from the response headers. For the fourth signal, the JS probe profile, you instrument the API surface itself, counting getClientRects, measureText, getParameter and friends per originating script. A consistency scanner captures exactly that profile, and it is what disambiguates two vendors that have both hidden their URLs.
Open-source detectors you will also see
Not every probe on a page is a commercial wall. Test sites and DIY setups reuse open-source detection libraries, and recognizing them keeps you from mistaking a hobby fingerprinting demo for a paywalled enterprise product. In recon we saw fpCollect.min.js (fingerprintjs/fpcollect, e.g. on bot.sannysoft.com and incolumitas), the FingerprintJS V5 bundle (BrowserScan), creep.js (Abraham Juliot’s all-in-one consistency and lie detector) and the Vastel-family cstlxp.js / device_info.min.js, often paired with a dedicated playwright.js that specifically probes for Playwright traces. If you see those filenames, you are looking at an open, inspectable detector rather than a black-box vendor, and the calculus changes accordingly.
The network-layer blind spot
One honest limitation: the signals above are all observed after the TCP/TLS handshake. Akamai, Cloudflare and DataDome all classify at the network layer too. JA3/JA4 TLS fingerprints and HTTP/2 frame ordering are computed before a byte of JavaScript executes. A target can bounce you at the handshake, and you will never see a datadome cookie or a tags.js request, because you never got that far. If your passive scan comes back empty but you are still being blocked, suspect TLS-layer classification and check your JA3/JA4 against a reference like tls.peet.ws before concluding the site is unprotected.
Takeaway
Identify before you engage. One navigation with a network log gives you script URLs, cookies and headers; score them across vendors and require at least two independent load-bearing signals to agree before you commit. Weight cookies and headers over URLs. Treat the loader hostname as decaying evidence in a world of first-party proxying, and fall back to the JS probe profile (the shape of the API access, not its origin) when the URL has been randomized away. And remember this is a 2026-07 snapshot: the signatures will drift, so build the matcher to combine signals and to be easy to re-baseline.