The Short Answer
The five enterprise WAFs Playwright operators run into most often — DataDome, Akamai Bot Manager, PerimeterX (now HUMAN), Kasada, and Imperva (Incapsula) — look like five different products and behave like four shared layers plus one proprietary layer each. The four shared layers are IP reputation, TLS / HTTP/2 fingerprint, browser fingerprint, and behavior / session state. The proprietary layer is whatever client-side SDK the vendor ships to collect signals the shared layers cannot reach.
Passing any one of them is not a property of "stealth" applied to a vanilla browser. It is the product of a clean IP, a real-browser TLS fingerprint, a browser fingerprint without patch shapes, plausible behavior, and survival of the vendor's specific SDK challenge. Each layer is independent. A clean fingerprint can pass three of them and fail the fourth.
This post walks through the five WAFs, the four shared layers, the one proprietary layer per vendor, and a practical matrix of what passing each one requires.
The Five WAFs at a Glance
A short orientation before the layer teardown.
DataDome. French vendor, founded 2015. Strong on e-commerce and ticketing. Customers include Reddit, Foot Locker, Tripadvisor, Hermès, Rakuten. Known for an aggressive client-side telemetry stream and cross-session device-graph linking.
Akamai Bot Manager. The incumbent on Fortune 500. Customers include most major airlines, banks, and retailers — Delta, United, Nike, Sony, Walmart. Distinguished by its Sensor Data SDK, which assembles a large client-side payload posted to /_bm/_data (or a customer-specific endpoint) and an _abck cookie that encodes the validation state.
PerimeterX (HUMAN). Acquired by HUMAN Security in 2022, sold under the BotDefender / Account Defender names. Customers include Crunchbase, Goat, StubHub. Known for its _px* cookie family and behavioral biometrics (mouse dynamics, touch event entropy).
Kasada. Australian vendor focused on the high-end of the market — sneaker drops, ticketing, financial APIs. Customers include Ticketmaster (in some regions), Canada Goose, Hyatt. Distinguished by its p.js challenge: heavily obfuscated JavaScript that runs a proof-of-work and emits a sensor payload before the page loads.
Imperva (Incapsula). The legacy enterprise WAF; older code base than the others, broad deployment in financial services and government. Distinguished by its reese84 token (a JS challenge result) and incap_ses_* session cookies.
Difficulty ordering, from easiest to hardest in practice on default configurations: Imperva → DataDome → PerimeterX → Akamai → Kasada. The reasoning lives in the proprietary-layer section below.
The Four Shared Layers
Every one of these vendors implements these four checks. The implementations differ in detail; the signal categories do not.
Layer 1: IP Reputation
A request arrives from an IP. Before any TLS handshake completes, the WAF checks ASN classification (datacenter vs residential vs mobile), shared blocklists (the WAFs cross-reference each other's flags through commercial threat intel feeds), prior history of that IP against the WAF's network, and rate windows.
The five vendors use overlapping data here. An IP burned on DataDome is often pre-flagged by PerimeterX and Akamai within hours, because the threat intel feeds they subscribe to are largely shared. This is why "I rotate proxies" loses effectiveness once a residential pool has been used aggressively across any one of these vendors.
- Pass: connection accepted, hands off to TLS.
- Fail: TCP reset, or a 403 with a vendor-specific block page.
Layer 2: TLS and HTTP/2 Fingerprint
The TLS ClientHello produces a JA3 or JA4 hash. HTTP/2 SETTINGS frames and pseudo-header order produce a separate H2 fingerprint. Default Node.js, Python requests, and Go net/http clients all have hashes that no real browser produces. WAFs maintain allowlists of "known-browser JA4" hashes and reject the rest at the edge.
This layer is invisible from a JavaScript perspective. The browser cannot fix it from inside the page; the TLS stack is set before any page code runs. curl-impersonate exists specifically to mimic this layer for HTTP-only flows.
- Pass: handshake completes with a recognized fingerprint.
- Fail: 403 at the edge, or in some configurations a silent redirect to a challenge page.
Layer 3: Browser Fingerprint and Automation Tells
Once the connection is established, the WAF's client-side script runs in the browser. It collects:
- Canvas fingerprint (rendering of a known string with known fonts).
- Audio fingerprint (output of a known audio context graph).
- WebGL renderer and vendor strings, plus the parameters that survive the
WEBGL_debug_renderer_infoextension. - Font enumeration (which fonts respond to measurement of common Unicode glyphs).
navigator.userAgent,navigator.platform,navigator.hardwareConcurrency,navigator.deviceMemory,screen.width/height/colorDepth.- Sec-CH-UA Client Hints (
sec-ch-ua,sec-ch-ua-mobile,sec-ch-ua-platform,sec-ch-ua-platform-version,sec-ch-ua-arch,sec-ch-ua-bitness). - Automation tells:
navigator.webdriver, the cluster of CDP-injected globals (__playwright__binding__,__pwInitScripts,cdc_*),Function.prototype.toStringaudits on overridden properties, the timing window in which patched properties were installed.
The five WAFs differ on which signals they weigh, but they all read this category. The cluster of weak signals matters more than any single check. Patching navigator.webdriver removes one tell; the model is still summing fifteen others.
- Pass: signals are consistent and within expected variance for the claimed browser.
- Fail: the score crosses a threshold, the request is dropped or escalated to the proprietary layer.
Layer 4: Behavior and Session State
The fourth shared layer is the only one that is not a single-snapshot check. It accumulates over time:
- Mouse paths: real mouse movement is curved, has acceleration, overshoots and corrects. Synthetic
page.mouse.move(x, y)calls produce straight lines with uniform velocity. - Typing cadence: real typing has variance in inter-key delay, occasional backspaces, and a distribution of keys-per-second that does not match
page.keyboard.type(text, { delay: 50 }). - Scroll behavior: real scrolls are not uniform pixel deltas; they are the output of a wheel or trackpad with momentum.
- Cookie age: a session presenting a
_px3or_abckcookie issued ten seconds ago is different from one presenting a cookie issued a week ago. - Site graph: a session that navigated to the protected route directly, with no referrer and no prior pages on the same domain, scores worse than one that landed on the home page first and clicked through.
The five WAFs differ in how aggressively they weight Layer 4, but they all read it. DataDome and Akamai weight it heavily; Imperva less so on default configurations.
- Pass: behavior is plausible for a human session.
- Fail: the session is escalated to a CAPTCHA, throttled, or silently degraded (some vendors return a successful-looking 200 with shadow-banned content).
The Proprietary Layer Per Vendor
Where the WAFs actually differ is in the SDK they ship to the page. This is the layer that gives each vendor its name.
DataDome: Real-Time Event Stream + Device Graph
DataDome's client SDK posts a continuous stream of events to its endpoint as the page is interacted with. The events include mouse movements, key events, focus changes, and timing data, all encrypted and packed into a payload that is rotated frequently. The server-side decision factors in the cross-session device graph: a fingerprint seen in a flagged session three days ago is associated with this session even on a different IP.
What makes DataDome distinctive is the rotation rate of its client-side code. The obfuscated JS is rebuilt on a short cycle, which makes static analysis perishable. Reverse-engineering the payload format on Tuesday gets you a parser that breaks on Friday.
The headers and cookies that identify a DataDome-protected site: set-cookie: datadome=..., x-dd-b: 1 on responses, and a 403 with a challenge page that includes t.captcha-delivery.com in iframe sources.
Akamai Bot Manager: Sensor Data + _abck
Akamai's SDK collects a large sensor payload — touch events, accelerometer (on mobile), keystroke timings, mouse paths, browser fingerprint snapshot — and posts it to a customer-specific endpoint (typically /_bm/_data or a path with akam substrings). The validation state of the session is encoded in the _abck cookie, which has a specific structure: an opaque string with a section that indicates "valid" or "invalid" status. A request with _abck in the invalid state is rejected at the edge regardless of other signals.
The Akamai SDK is the most studied of the five — there are open-source projects that attempt to reproduce its sensor payload — and it remains one of the hardest to fully solve, because the customer-specific endpoint and customer-specific obfuscation seed make every deployment slightly different. A solver tuned for one Akamai customer often does not work for another.
The headers that identify an Akamai-protected site: set-cookie: _abck=..., set-cookie: bm_sz=..., set-cookie: ak_bmsc=....
PerimeterX (HUMAN): _px* Cookies + Behavioral Biometrics
PerimeterX's distinguishing layer is its behavioral biometrics analysis. The SDK collects mouse dynamics (path curvature, click duration, hover-before-click time), touch event entropy on mobile, and form interaction patterns (paste detection, autofill detection, focus-blur cycles). The result is encoded in a family of cookies: _px, _pxhd, _pxvid, _px2, _px3 — different versions for different SDK generations.
The _px3 cookie in particular is a hash of the session fingerprint plus a server-issued nonce, validated server-side. A replayed _px3 from a different fingerprint fails. This makes cookie-replay attacks on PerimeterX harder than on the other vendors.
The headers that identify a PerimeterX-protected site: set-cookie: _px*=..., x-px-* headers, a 403 that loads captcha.px-cdn.net resources.
Kasada: p.js Proof-of-Work + Sensor
Kasada's distinguishing layer is the p.js script. It is a heavily obfuscated JavaScript challenge that runs a proof-of-work computation (a real CPU-burning hash loop, not a placebo) and emits a sensor payload before the page renders. The proof-of-work serves two purposes: it makes high-throughput bypass economically expensive (a fleet of solvers burning CPU on every request adds up), and it gates the page on having a JavaScript engine that actually executes the workload.
Kasada is the most expensive WAF to bypass at scale. Solving services charge measurably more per request for Kasada than for DataDome or PerimeterX, and the success rate of cookie-replay strategies is low because the proof-of-work answer is bound to the request.
The headers that identify a Kasada-protected site: set-cookie: x-kpsdk-cd=..., set-cookie: x-kpsdk-ct=..., requests to /ips.js or paths containing KP-SDK.
Imperva (Incapsula): reese84 + JS Challenge
Imperva's distinguishing layer is the reese84 token. A site protected by Imperva returns a 403 with an embedded JavaScript challenge on the first request. The script computes a token — derived from the browser fingerprint, a server-issued seed, and timing data — and posts it back to a /_Incapsula_Resource endpoint. The server validates the token and issues incap_ses_* cookies that the client carries on subsequent requests.
Imperva's challenge is the oldest of the five and the most documented. The reese84 algorithm has been reverse-engineered multiple times publicly, which is why Imperva ranks easier on the difficulty scale — but Imperva's deployment in regulated industries (banking, government) means the customer-side configurations are often paired with strict IP allowlists, which moves the difficulty back up.
The headers that identify an Imperva-protected site: set-cookie: incap_ses_*=..., set-cookie: visid_incap_*=..., x-iinfo headers, requests to /_Incapsula_Resource.
Difficulty Ordering, Explained
The ordering — Imperva → DataDome → PerimeterX → Akamai → Kasada — comes from how each vendor weights the proprietary layer relative to the shared four.
- Imperva is easiest on default config because the
reese84algorithm is reverse-engineered and the JS challenge is largely self-contained. A clean browser fingerprint plus a residential IP plus a workingreese84solver passes most Imperva sites. The hard cases are the regulated-industry deployments with explicit allowlists, but those are gating on identity, not on bot detection. - DataDome is harder than Imperva because the client SDK rotates frequently and the device graph cross-references sessions. A solver pinned to last week's payload format breaks. But the four shared layers do most of the work; passing them with a clean browser usually gets the request through.
- PerimeterX sits in the middle because the behavioral biometrics layer is genuinely hard to fake. A request with no mouse movement before the click, or with a robotically uniform mouse path, fails the
_px3validation even when the browser fingerprint is clean. - Akamai is hard because the sensor payload is large, customer-specific, and bound to the session in ways that make cookie-replay unreliable. The customer-specific obfuscation seed means every Akamai customer is effectively a different problem.
- Kasada is hardest because the proof-of-work makes scale economically painful and the
p.jsscript is the most aggressive on browser-environment validation. Kasada will reject browsers that pass DataDome and PerimeterX cleanly.
This ordering is for default configurations. A customer can dial up the strictness on any of these vendors, and the ordering reshuffles. An aggressive Imperva configuration can be harder than a default-config Kasada deployment.
What Passing Each One Actually Requires
The matrix below summarizes what every one of these WAFs needs to pass on a default-strict configuration.
| WAF | Residential IP | Real-Browser TLS | Engine-Level Browser | Behavioral Sim | SDK-Specific Handling |
|---|---|---|---|---|---|
| Imperva | Recommended | Required | Recommended | Optional | reese84 solver |
| DataDome | Required | Required | Required | Required | Real-time event stream |
| PerimeterX | Required | Required | Required | Required (biometrics) | _px3 validation |
| Akamai | Required | Required | Required | Required | Sensor payload reproduction |
| Kasada | Required | Required | Required | Required | p.js execution + proof-of-work |
A few notes on this table:
- Engine-level browser means a Chromium build with C++ layer fingerprint patches, not a vanilla Chromium with JavaScript-injected overrides. The four shared layers (especially Layer 3) read patch shapes, not just patched values.
- Behavioral sim is binary in this matrix but actually a spectrum. PerimeterX needs the full spectrum; Imperva often passes with synthetic but plausible behavior.
- SDK-specific handling is the most variable cell. "Required" means without it the WAF will not pass on a default-strict configuration; "Recommended" means it helps but the shared layers may carry the request through.
Diagnostic Checklist
When a Playwright session fails on a WAF and you need to locate which layer rejected you:
- Identify the WAF from response headers. The
set-cookie,server, andx-*headers are diagnostic._abck→ Akamai.datadome→ DataDome._px3→ PerimeterX.x-kpsdk-ct→ Kasada.incap_ses_*→ Imperva. Knowing the vendor narrows the proprietary layer immediately. - Check the response status. A 403 at the edge (no body, or a vendor block page) usually means Layer 1 or 2. A 200 with a challenge HTML body means you reached Layer 3 or the proprietary layer. A 200 with the actual content but missing data usually means Layer 4 shadow-banned the session.
- Inspect the TLS handshake separately. Run
curl -v --tlsv1.3 https://targetand compare the JA3/JA4 hash againsttools.peetch.comorja4db.com. If the TLS handshake fails outright, no JavaScript layer matters. - Probe the browser environment in isolation. Run the same Playwright session against
bot.sannysoft.comandcreepjs. If those pass and the target fails, the problem is the IP or the SDK-specific layer, not the browser. - Change one variable at a time. Different proxy. Different browser engine. Add session warm-up. Do not change all three at once; you will not learn which fixed it.
Quick FAQ
Q: Which is hardest — DataDome, Akamai, or PerimeterX?
On default configurations: Akamai > PerimeterX > DataDome. Akamai's sensor-payload reproduction is the most fragile to maintain. PerimeterX's behavioral biometrics layer rejects mechanically generated input. DataDome is the most permissive of the three when the four shared layers are clean. Kasada is harder than all three; Imperva is easier than all three.
Q: Can I tell which WAF a site uses from response headers?
Yes, and this is the first diagnostic step. The cookie names are diagnostic: _abck for Akamai, datadome for DataDome, _px* for PerimeterX, x-kpsdk-ct for Kasada, incap_ses_* for Imperva. Vendor-specific headers also exist (x-iinfo for Imperva, x-px-* for PerimeterX). A site can also be behind multiple WAFs in series — Cloudflare in front of Akamai is a common stack — in which case the headers from both will be present.
Q: Do these WAFs share detection signals?
Yes, through commercial threat intel feeds. An IP burned on DataDome is often pre-flagged by Akamai and PerimeterX within a short window. Browser fingerprints flagged on one vendor's network can leak into another's via shared third-party data brokers. This is why aggressive proxy rotation against any one of these vendors degrades performance against all of them.
Q: Is there a single tool that bypasses all five?
No. The four shared layers can be addressed by a clean engine-level browser plus residential proxies plus session warm-up, and that gets a long way. The proprietary layer per vendor requires either reproducing the SDK output (which is fragile, since the SDKs rotate) or letting the real SDK run in a real browser (which works but is slower). The trade-off is per vendor and per use case.
Q: What about CAPTCHA solver services for these WAFs?
Solver services exist for the challenge step of each (DataDome's t.captcha-delivery.com widget, PerimeterX's CAPTCHA, etc.). They produce a token by running the challenge in their own browser pool. The token is bound to the calling IP and fingerprint in most of these vendors, so a token bought from a solver fails validation if your request reaches the server with a flagged IP or a different fingerprint. Solvers complement a clean stack; they do not replace one.
Q: How does the engine-level browser layer interact with the proprietary SDK?
The proprietary SDK runs as JavaScript in the page. An engine-level browser with C++ fingerprint patches affects what the SDK reads — the SDK's calls to navigator.userAgent, canvas.toDataURL(), AudioContext, and the rest return values consistent with a real browser, with no patch shapes. The SDK still runs and still emits its payload; the difference is that the payload looks like one a real browser would emit. The SDK does not know it is being read by a patched build, because there is nothing in the C++ layer that exposes the patches.
Bottom Line
The five enterprise WAFs are not five different problems. They are four shared layers — IP, TLS, browser fingerprint, behavior — plus one proprietary SDK each. Passing the shared four is the larger half of the work, and an engine-level browser plus a residential proxy plus session warm-up handles it. The proprietary fifth is per-vendor, per-deployment, and the part that determines whether a stack works on Akamai but breaks on Kasada.
The right way to think about a Playwright stack against these WAFs is not "which stealth approach beats DataDome" but "is my stack clean across the four shared layers, and which of the five proprietary fifth-layer challenges am I willing to take on". The shared four are reusable across vendors. The proprietary fifth is specific work each time.
If you are evaluating browser engines for the shared four-layer foundation, BotCloud provides an engine-level Chromium build with a per-context fingerprint API and the standard Playwright/Puppeteer attachment surface — the foundation, not the per-vendor SDK work.