Imperva's reese84 sensor: what the obfuscated payload collects
Load a site behind Imperva’s Advanced Bot Protection and, somewhere in the network panel, a request goes out to a path you did not click on, carrying a body of text/plain that looks like a wall of escaped JSON. Seconds later a cookie named reese84 appears, holding a string that starts with a digit, a colon, and then a few hundred bytes of base64. No CAPTCHA, no interstitial, no visible challenge. The page just works. That cookie is the proof-of-work output of a client-side sensor that ran while you were reading, measured roughly two hundred things about your browser and device, encrypted them, and shipped them to an endpoint that minted the token you are now carrying on every subsequent request.
This post is about that sensor and nothing else. Not the older ___utmvc script, not the server-side scoring model, not the visid_incap and incap_ses cookie chain that sits alongside reese84, though all of those connect to it. The question here is narrower. What does the reese84 JavaScript actually collect, how is the collector obfuscated so that reading the source tells you almost nothing, where does the payload go, and how does the response become the token? Imperva does not publish the field layout, so where the detail comes from reverse-engineering rather than vendor documentation, this post says so.
The walk goes like this. First, where reese84 sits in Imperva’s lineage and how it differs from the legacy script. Then the shape of the obfuscated collector and the techniques that hide it. Then the telemetry itself, grouped by what it measures and why. After that, the interrogation request and the token-minting handshake, including the version prefix and the renewal clock. Finally, what the design tells you about where Imperva put its detection, and how that has moved since 2022.
Where reese84 comes from
The bot-detection machinery behind reese84 is not native Imperva work. It arrived through acquisition. Imperva, founded in 2002 as WebCohort, bought the cloud-security company Incapsula in 2014, then acquired the bot-management specialist Distil Networks in July 2019. The Distil deal is the one that matters for reese84: it folded a dedicated bot-mitigation engine into what Imperva now markets as Advanced Bot Protection. Imperva itself was acquired by Thales for 3.6 billion dollars, a deal that closed on 4 December 2023. So the system you are interrogating today is a Distil-derived sensor, running on Incapsula’s edge, owned by Thales. The “Incapsula” name lingers in cookie prefixes and resource paths long after the brand was retired.
That lineage explains why two different challenge scripts coexist on Imperva-protected sites. The older one drops the ___utmvc cookie. The newer one drops reese84. A given site may serve one, the other, or both, depending on configuration. The ___utmvc script is the more documented of the two because it has been around longer and its obfuscation, while real, is more tractable. reese84 is the harder target and the one Imperva has invested in. Where utmvc computes a value once and posts it to a path containing _Incapsula_Resource, reese84 runs a longer-lived collector, talks to a dedicated interrogation endpoint, and gets back a token with an expiry that the script is expected to refresh on a timer.
Imperva’s own description of the product stays at altitude. The marketing material claims the system can “detect over 700 dimensions to separate between human, good, and bad bot traffic,” and describes the detection as a blend of “direct client interrogation, behavior analysis, machine learning (ML), connection characteristics, and threat intelligence feeds.” Direct client interrogation is the reese84 sensor. The 700 dimensions are a server-side scoring claim, not a count of fields in the payload; the payload itself carries on the order of two hundred encrypted values, a figure that comes from community measurement rather than vendor documentation. Treat the 700 as marketing and the ~200 as observed.
It helps to be precise about what “reese84” names, because the term gets stretched. It is the cookie name first of all. By extension it is the token that lives in that cookie, the JavaScript collector that mints the token, and the endpoint the collector talks to. When a reverse-engineer says they are “solving reese84,” they mean reproducing the collector’s output well enough that the endpoint returns a valid token. When this post says reese84, it means the whole client-side mechanism: the script, its payload, the request, and the credential that comes back. The cookie is just the visible tip of it.
How reese84 differs from the legacy utmvc script
The two scripts solve overlapping problems with different shapes, and the differences are worth stating plainly because most of the older public analysis is about utmvc, not reese84, and the lessons do not transfer cleanly. The utmvc script computes a fingerprint value, base64-encodes it, and POSTs it once to a path containing the literal _Incapsula_Resource, after which the server sets the ___utmvc cookie. It is fundamentally a single-shot proof: collect, submit, done. Its obfuscation is real but its lifecycle is simple, and because the resource path contains a recognizable literal, the request is easy to spot in a trace.
reese84 inverts most of that. The path is randomized per site rather than literal, so there is no fixed string to grep for in the network panel; you find it by looking for the ?d= query parameter naming the protected host. The collection is heavier and the value space larger. And the credential renews. Where utmvc mints a value and forgets about it, reese84 hands back a renewInSec interval and expects the client to come back and mint again before that window closes, carrying the previous token forward in an old_token field so the session chains. A site can deploy either script, or both at once, and the configuration is per-deployment. The practical consequence is that a client that has learned to satisfy utmvc has learned almost nothing about reese84: different path discovery, different payload encoding, different token format, and a renewal obligation that utmvc never had.
The shape of the obfuscated collector
The reese84 sensor is delivered as a single JavaScript file fetched from a per-site path. The path is not a fixed string. It is randomized per deployment, and the request that loads it carries a ?d= query parameter naming the protected domain, something on the order of https://target.com/some-random-path?d=www.target.com. That same path, hit with a POST instead of a GET, is the interrogation endpoint. The GET hands you the collector; the POST submits its output. One path, two verbs.
Reading that file does not get you far. The script is processed through the open-source obfuscator.io toolchain, and the choices that tool makes are visible in the output. Strings are stored hex-escaped, with \x sequences instead of literal characters. Identifiers are renamed to hexadecimal gibberish. Numeric literals are rewritten as hex. The real defense is a string-array indirection: every meaningful string in the program is pulled out into one big array, the array is shuffled at load time by rotating it a fixed number of times (the “magic number,” a value like 0x14a that the shuffle loop decrements to), and every reference to a string in the body becomes a call into an accessor function with an index argument. So navigator.webdriver does not appear as text anywhere. It appears as something like _0xe3ea("0x44", "@7QD"), a lookup that returns the decrypted string at runtime.
That accessor is not a plain lookup. The strings in the array are themselves encrypted, and the accessor decrypts on demand using RC4. The community write-up that traced this through the utmvc variant documented the exact shape: each string is base64-decoded, then run through an RC4 keystream where the key is the second argument to the accessor call, the S-box is initialized over 256 iterations, the key-scheduling algorithm permutes it, and the keystream is XORed against the bytes. RC4 is weak as a cipher, but it is not being used for secrecy against a determined analyst. It is being used to keep strings out of a grep and to make the script expensive to read statically. The same obfuscation family wraps reese84.
*The obfuscator.io string-array pattern: a hex index and an RC4 key resolve through a shuffled array to a runtime-decrypted string, so property names never appear as literals in the source.*Layered on top of the string handling is control-flow flattening, where the natural nesting of functions is replaced by a single dispatch loop driven by a state variable, so the order of operations is not visible from the indentation. The collection functions themselves are anonymous and assigned dynamically, and there are roughly twenty of them in current builds, a count that drifts upward as Imperva adds probes. Each one computes some fingerprint value and writes it into a slot in the payload structure. Because the functions are anonymous and the slots are addressed by obfuscated keys, a static read tells you a function exists and that it writes somewhere, but not what it measures or where the result lands. You have to run it.
If you have read the companion pieces on Kasada’s KPSDK VM or Akamai’s sensor_data payload, the family resemblance is obvious. Every serious client sensor converges on the same defensive shape: pull the strings out, encrypt them, flatten the control flow, randomize the delivery path, and rotate the build often enough that a static deobfuscation goes stale. reese84 is a competent instance of that pattern rather than an exotic one. What distinguishes it is less the obfuscation than what the de-obfuscated collector goes and measures.
What the payload collects
Once the collector runs, it walks the browser environment and assembles a structure of measured values. The exact key names are obfuscated and rotate between builds, so the field-level layout below is grouped by what the probes target rather than by literal key, and it is inferred from observed traffic and from the open-source generators that have been built against the script, not from any Imperva document. With that caveat, the categories are consistent across analyses.
The largest category is rendering fingerprints. The script draws to a 2D canvas and reads the pixels back, exercising font rasterization, anti-aliasing, and sub-pixel rendering, which vary by GPU, driver, and OS in ways that are stable per device and different across devices. It queries WebGL for the renderer and vendor strings (the unmasked WEBGL_debug_renderer_info values, where available) and for the long list of supported extensions and parameter limits. It instantiates an AudioContext and measures the output of an oscillator-through-compressor graph, the standard audio-fingerprint trick, where floating-point differences in the audio stack produce a stable hash. Canvas, WebGL, and audio together are the bulk of the entropy, and they are exactly the surfaces that anti-detection browser builds patch at the C++ level rather than in JavaScript, because a JavaScript-level spoof is trivially caught by the consistency probes described below.
The second category is the navigator and environment surface. The script reads navigator.userAgent, platform, vendor, appName, language and languages, hardwareConcurrency, deviceMemory, and the plugins and MIME-type lists. It enumerates installed fonts, typically by measuring the rendered width of a probe string across a font list and noting which fonts change the metrics. It reads screen dimensions, color depth, and devicePixelRatio. It checks the timezone and the locale. None of these is individually identifying, but the combination is, and more importantly the combination has to be internally consistent. A platform of Win32 with a macOS-shaped WebGL renderer string is a contradiction the server can score on.
The third category is automation detection, and this is where the design earns its keep. The script reads navigator.webdriver, the W3C-standardized flag that a compliant automation driver sets to true. It looks for the property names that headless and automation frameworks inject into the page, the _phantom, __nightmare, callPhantom, and various Selenium and cdc_-prefixed markers that older tooling leaked into the document or window. It probes for the Chrome DevTools Protocol, the wire interface that Puppeteer and Playwright drive Chromium through, because an attached CDP session changes observable runtime behavior. It checks whether native functions have been overridden by calling Function.prototype.toString on them and looking for anything other than the [native code] marker, which is how a JavaScript-level spoof of, say, navigator.webdriver gives itself away. The mechanics of CDP detection and runtime-patch detection get a fuller treatment in the piece on Kasada’s anti-instrumentation, and the principles transfer directly: the cheapest signal is the one the automation tool sets on itself.
A subtle point about automation detection is that the sensor cares as much about the shape of the lie as about the underlying truth. A driver that sets navigator.webdriver to true is caught directly. But a client that patches navigator.webdriver to return false in JavaScript is caught indirectly, because the patch leaves fingerprints of its own: the property descriptor changes, the getter is no longer the native one, and Function.prototype.toString on the accessor no longer returns [native code]. The sensor probes for the patch, not just the value. This is the recurring asymmetry in client interrogation. The honest browser exposes a small, consistent surface; the spoofed browser exposes that surface plus the seams of every patch applied to it, and the seams are easier to detect than the original values were. A spoof that hides one signal tends to create three new ones.
The fourth category is the quiet one: consistency and timing. The script does not only collect values, it cross-checks them, and it measures how long the collection took. A performance-derived timing object rides along in the payload. Real browsers on real hardware produce collection timings within a band; an environment where the JavaScript engine is instrumented, or where probes are being intercepted and answered by hooks, produces timings outside it. A hook that intercepts a property read and returns a forged value adds a few microseconds to that read, and across a collection that touches hundreds of properties, the accumulated overhead is measurable. The timing object is cheap to collect and expensive to forge convincingly, because forging it well requires knowing the band the server expects, which the server does not advertise.
The cross-checks look for the contradictions a spoofer introduces: a user-agent claiming Chrome on Windows while the WebGL vendor says Apple, a touch-capable navigator.maxTouchPoints on a platform string that says desktop, a language list that disagrees with the timezone, a hardwareConcurrency that does not fit the device class the rest of the fingerprint implies. None of these checks is novel; every mature sensor runs some version of them, and the ones in DataDome’s detection model and F5 Shape’s signal set overlap heavily. What matters is that the payload is built to make lying expensive. Any single spoofed value forces consistent spoofing of every value that could contradict it, and the number of values that could contradict any given field is large enough that maintaining global consistency by hand is impractical. That is the design intent: not to make a single value hard to forge, but to make the joint distribution hard to forge.
The interrogation request and the token
With the structure assembled, the script encrypts it and POSTs it back to the same path it was loaded from. The transport details are observable even though the plaintext is not. The request body is sent as Content-Type: text/plain; charset=utf-8 rather than application/json, which keeps it a simple request and sidesteps a CORS preflight. The body is a JSON envelope whose main field, commonly seen as p, holds the encrypted payload, alongside short fields that community generators have labelled st (a timestamp), sr and cr (encoded cryptographic values used in the handshake), and og (a version or origin indicator). A version string and an old_token field also appear; the old_token carries the previous token forward on renewal so the server can chain a session rather than treat every mint as a cold start. These field names are from open-source generators and observed traffic, not from Imperva, and they have changed across builds.
The encryption of the payload is not the RC4 used for the string array. The collector serializes its values and runs them through a byte-shuffling layer built on a xorshift128 pseudo-random generator, with dynamic loops that “shuffle, copy, clone, or re-arrange the bytes in a dynamic order,” as one of the generator projects describes it. The PRNG is seeded so that the server, knowing the seed material carried in the envelope, can reverse the permutation and recover the plaintext. The point of the scheme is not cryptographic strength. It is to bind the encoding to this specific build of the script, so that a payload cannot be assembled without first having extracted the exact obfuscated collector that the site is currently serving. Rotate the build, and last week’s encoder is wrong.
There is a reason the encoding is build-bound rather than keyed to a stable secret. A stable key could be lifted once and reused indefinitely; a build-bound encoder forces re-extraction every time the script rotates. The seed material travels in the envelope precisely so the server can reverse the permutation without the client and server sharing a long-lived secret, which means there is nothing durable to steal. The cost it imposes is asymmetric in Imperva’s favor: shipping a new build is cheap and automated on their side, while re-deriving the encoder from a fresh obfuscated blob is manual and slow on the other side. The whole scheme is an exercise in keeping that asymmetry wide.
The response is small and structured. The fields that matter are token, renewInSec, and cookieDomain. The token is the value that lands in the reese84 cookie, and it carries a version prefix: current builds emit a token that begins 3:, followed by colon-separated base64 segments, a shape that appears both in the freshly minted token and in the old_token field of the next request. The 3: is a format version, and the fact that it is now 3 rather than 1 or 2 tells you the token structure has been revised at least twice. The renewInSec field is the clock: it tells the script how long the token is good for, in seconds, after which the collector is expected to run again and mint a replacement. This is the difference that matters between reese84 and the one-shot ___utmvc model. reese84 is not a single proof at page load. It is a renewing session credential, and a client that holds a token past its renewInSec window without refreshing it is a client the edge can flag.
That token then anchors the rest of the cookie chain. Imperva’s edge issues and tracks visid_incap_* as a visitor identifier and incap_ses_* as a session cookie, with nlbi_* used for load-balancer affinity, and a valid reese84 token is what keeps those in a “passed” state. Without a matching, current token, subsequent requests get challenged again or blocked. The relationship between reese84 and that Incapsula cookie family is the subject of the companion post on Incapsula bot detection and the ___utmvc cookie; the short version is that reese84 is the proof and the incap cookies are the session state that proof unlocks.
Where the detection actually lives
Two things stand out about reese84 once you have traced it end to end. The first is that the client sensor is deliberately not the whole story. The payload is rich, but Imperva’s own description puts client interrogation as one of five inputs alongside behavior analysis, machine learning, connection characteristics, and threat intelligence. The connection-characteristics input is the one the sensor cannot help with: the JA3 and JA4 TLS fingerprint, the HTTP/2 frame settings, and the header ordering are all read at the edge from the wire, below the level where any JavaScript runs. A reese84 payload that decrypts to a perfectly consistent, perfectly human-looking environment, delivered over a TLS handshake that does not match the user-agent it claims, is a contradiction the sensor never sees and the edge always does. The mechanics of that wire-level signal are covered in the piece on TLS fingerprinting from JA3 to JA4, and they are why a flawless sensor payload is necessary but not sufficient.
The second is the economics of the build rotation. Every defensive choice in reese84, the per-site randomized path, the RC4 string array, the build-specific xorshift encoding, the renewing token, points at the same goal: making the cost of staying current high and recurring. A static deobfuscation is a one-time cost that pays off until the next build ships, and the next build ships often. The token format moving to 3: and the collection-function count creeping past twenty are both visible markers of that churn. The design assumes its own obfuscation will be broken and is built so that breaking it once buys less than it looks like it should.
What that leaves is a sensor whose job is narrower than its size suggests. It is not there to be unbreakable. It is there to force any client that wants a token to run the current collector, in something close to a real browser environment, and to do so consistently enough that the cross-checks and the wire-level signals it cannot control still line up. The ~200 encrypted values are the visible part. The contradictions between them, and between them and the TLS handshake underneath, are where the verdict is actually made.
Sources & further reading
- Imperva (2025), Advanced Bot Protection — vendor product page; source of the “over 700 dimensions” and “direct client interrogation” detection claims.
- yoghurtbot (2023), Deobfuscating Imperva’s utmvc Anti-Bot Script, Part 1 — detailed reverse-engineering of the obfuscator.io string-array, magic-number shuffle, and RC4 accessor used in the utmvc and reese84 family.
- BottingRocks (2024), Incapsula payload generator for reese84 and ___utmvc — open-source generator; source of the
p/st/sr/cr/og,old_token,performance, andversionfield labels and the xorshift128 byte-shuffling description. - Hyper Solutions (2025), reese84 API documentation — documents the GET-collector / POST-interrogation flow, the
?d=path parameter, thetoken/renewInSec/cookieDomainresponse fields, and the3:token prefix. - Scrapfly (2026), Bypass Incapsula / Imperva — maps the
reese84,incap_ses_*,visid_incap_*, andnlbi_*cookie chain and the canvas/WebGL/AudioContext/navigator/font collection surface. - Roundproxies (2026), How to bypass Imperva Incapsula in 2026 — the “180+ encrypted values” figure and the JA3/JA4,
Sec-CH-UA, andSec-Fetch-*edge signals. - Imperva (2019), Imperva to Acquire Distil Networks, the Leader in Bot Management — the July 2019 acquisition that folded a dedicated bot engine into Advanced Bot Protection.
- Wikipedia (2025), Imperva — corporate history: 2002 founding, 2014 Incapsula acquisition, July 2019 Distil acquisition, and the $3.6B Thales deal closing 4 December 2023.
- obfuscator.io (2024), JavaScript Obfuscator Tool — the open-source toolchain whose string-array, hex-escape, and control-flow-flattening features match the reese84 output.
- W3C WebDriver (2024), WebDriver specification, navigator.webdriver — the standard flag the sensor reads as its cheapest automation signal.
Further reading
Akamai's sensor_data payload: the fields and their telemetry sources
A field-by-field tour of the sensor_data payload Akamai Bot Manager POSTs from the browser: what telemetry it carries, how the numeric field markers are laid out, and how the obfuscation and encryption have changed across v1, v2, and v3.
·19 min readDataDome's detection model: every signal it collects on the first request
Traces what DataDome evaluates on the very first request, before any JavaScript runs: the TLS/JA4 fingerprint, the HTTP/2 frame profile, the header set, and IP and ASN reputation, and how those signals stack into one decision.
·19 min readInside the DataDome JS tag: what ddjskey and the client payload carry
A reference on DataDome's client-side JavaScript tag: the ddjskey site identifier, the signals the browser collector gathers and posts to api-js.datadome.co, and how the challenge and interstitial flow is wired.
·21 min read