Skip to content

Akamai's bm_sz cookie and pixel challenge: the second layer most clients miss

· 22 min read
Copyright: MIT
The string bm_sz rendered as a monospace wordmark with an orange underline bar

Most write-ups on Akamai Bot Manager treat the whole system as one problem with one answer: mint a valid _abck, keep it valid, scrape. That advice is correct as far as it goes, and it goes about two-thirds of the way. The remaining third is where clients that pass every _abck check still find themselves staring at a 428, a redirect into an interstitial, or an HTML body that is not the page they asked for. The reason is almost always the same. There is a second layer, and it does not announce itself the way _abck does.

That second layer is a small cluster of cookies and challenge types that live beside the sensor handshake rather than inside it. The bm_sz cookie that gets set before any JavaScript runs. The pixel challenge that quietly mints ak_bmsc off a fingerprinting beacon. The sec-cpt proof-of-work interstitial that throws a 428 and a countdown timer in front of you when the score crosses a threshold. None of these are the _abck validation handshake, and a client built to only handle _abck treats them as noise until it gets blocked. This post is about what they are, when they fire, and why they are easy to miss.

The sections below take them in order. First, where bm_sz sits in the cookie family and why it is an input to the sensor encoding rather than a passive label. Then the pixel challenge: the fingerprinting beacon, the bazadebezolkohpepadr variable it keys off, and the ak_bmsc cookie it produces. Then the sec-cpt 428 challenge, its three provider types, and the SHA-256 proof-of-work that gates it. Finally, why the layers are separate by design, and what a client misses when it models only the first one.

Where bm_sz sits, and why it is not just a label

Akamai Bot Manager does not keep its state in a single cookie. It sets a small cluster, and each one has a job. The cookie database at cookie.is, which catalogues observed cookies across a large crawl, lists the Akamai family with their durations: _abck at 365 days, bm_sz at 4 hours, ak_bmsc at 2 hours, plus the shorter-lived bm_sv and bm_mi. All of them are flagged as Akamai bot-management or fraud-prevention cookies. The long-lived one is _abck. The interesting short-lived ones, for our purposes, are bm_sz and ak_bmsc.

bm_sz is set first. It usually arrives on the very first HTML response from a protected origin, before a single line of the Bot Manager JavaScript has executed. That ordering is the whole point. The sensor script that eventually produces a sensor_data payload needs a seed to work from, and bm_sz is that seed. It is set server-side, typically marked HTTP-only, and scoped to roughly a four-hour window. A fresh browser hitting a protected page gets a bm_sz whether or not it ever runs the script. Which means its presence tells you nothing. Its content, and the fact that the later sensor payload is bound to it, is what matters.

The binding is the part people skip. In the version 3 sensor format, a value derived from bm_sz feeds the encoding of the sensor_data blob that the client posts back. The exact derivation is not documented by Akamai, and anyone claiming the precise byte recipe is reading a deobfuscation of the sensor script rather than a spec. What is observable in traffic, and consistent across the public SDKs that target Akamai, is that you cannot generate a valid sensor for a session using a bm_sz captured from a different session or a different origin. The cookie is an input to the cryptographic step, not a name tag stapled to the side of it. That coupling is why a scraper that reuses cookies across hosts, or carries a stale bm_sz past its window, gets a sensor rejected even when every other field looks right.

When each Akamai cookie is set bm_sz first HTML response, ~4h ak_bmsc after pixel beacon, ~2h _abck after sensor POST, ~365d sec_cpt only if 428 interstitial fires bm_sz is the seed. The sensor payload that mints _abck is bound to it, so a stale or cross-origin bm_sz invalidates everything downstream. *The cookie that arrives first is the one most clients treat as inert. It is an input to the sensor encoding, not a passive session label.*

There is one more reason bm_sz deserves attention before the challenges. It is the cookie a server-side decision can quietly rotate. If Bot Manager wants to re-run its first-request assessment, refreshing bm_sz forces the client to regenerate a sensor bound to the new value. A client that caches the old bm_sz and keeps posting sensors derived from it looks, from the edge, like something replaying a recording. For the full picture of how the sensor payload itself is assembled and what telemetry it carries, the companion post on Akamai’s sensor_data payload covers the fields; here the point is narrower, that bm_sz is upstream of all of it.

Telling the cookies apart by what they are for, not what they look like

A practical problem with the Akamai cookie family is that every member looks like the same kind of thing in a network panel: a long, opaque, tilde-and-base64 value scoped to the protected host. They are not the same kind of thing, and conflating them is the first step toward modelling only one layer. The cleanest way to keep them straight is by what each one does, how long it lasts, and the moment it appears.

_abck is the long-lived verdict. It carries a 365-day expiry, which is a giveaway that Akamai intends it to follow a browser across visits and sessions, and its value is the running summary of what the sensor has decided about the client. The public Go SDK exposes a function, IsCookieValid, that reads the _abck value together with a request count to decide whether the session is trusted, and another, IsCookieInvalidated, that detects the invalidated state where the value ends in a ~0~-1~-1 style marker and the client must post one more sensor to recover. That tilde-delimited status field is the thing most _abck-centric tooling keys off, and it is genuinely the right thing to watch for the first layer. It is just not the only state that matters.

bm_sz is the short-lived seed, four hours, set before script execution, an input to the sensor encoding. ak_bmsc is the pixel-beacon product, two hours, set in response to the fingerprinting POST, and on caching-enabled origins it doubles as a performance cookie, which is part of why it is easy to dismiss as infrastructure. sec_cpt only exists if the interstitial has fired and been solved; on a session that never tripped a 428 you will never see it. The durations are not arbitrary. A four-hour seed and a two-hour beacon cookie mean Akamai gets to re-assess a long-running client several times a day without touching the year-long _abck, and each re-assessment is a fresh opportunity for a replayed or stale value to give a scraper away.

The reason this matters for a client author is that the four cookies fail in four different ways, and the failure modes do not resemble each other. A burned _abck produces an obvious challenge or block on the next request. A stale bm_sz produces a sensor rejection that looks like a sensor bug. A missing ak_bmsc produces a session that the edge treats as incomplete on pixel-enabled pages, with no clear error pointing at the cause. A missing or expired sec_cpt produces a recurring 428 on exactly the paths that escalate. Four cookies, four symptoms, and only one of them announces itself plainly.

The pixel challenge is the layer people most often do not know exists. It sits apart from both the sensor handshake and the interstitial, a separate lightweight fingerprinting beacon that runs on some protected pages and produces the ak_bmsc cookie. The name comes from the tracking-pixel pattern: a small script collects a bundle of device and browser signals, posts them to an Akamai endpoint, and the response sets a cookie. On pages where it runs, ak_bmsc is part of the trusted-session state, and a request without a properly minted one looks incomplete.

What makes the pixel challenge awkward to handle is how it is wired into the page. The trigger lives in two pieces of the HTML that have deliberately opaque names. The first is a numeric variable the public Akamai SDKs call the HTML var, parsed from a script element by a function named, in the Hyper Solutions Go SDK, ParsePixelHtmlVar. The second is a string value hidden inside the first array of the pixel script itself, parsed by ParsePixelScriptVar. Together with the script URL and the POST URL, recovered by ParsePixelScriptURL, those two values are what the beacon needs before it can fire.

The string variable has a memorable name. In observed Akamai pixel scripts it is assigned to a global called bazadebezolkohpepadr. That is not a typo and not an acronym anyone has decoded publicly; it is the literal identifier the obfuscated script uses to stash the value the beacon will echo back. Several independent reverse-engineering write-ups and the public SDKs all key off the same string, which is what gives confidence it is real rather than site-specific. The exact transformation applied to it before the POST is not documented by Akamai, and the working code that performs it is exactly the kind of thing this blog does not republish. The mechanism, at the level that helps you understand traffic, is straightforward: read bazadebezolkohpepadr and the numeric HTML var out of the page, run them through the pixel script’s logic alongside collected device signals, POST the result, receive ak_bmsc.

Pixel challenge flow HTML page html var (int) bazadebezol... pixel script device signals + script var beacon POST to pixel URL ak_bmsc set in response, ~2h *The beacon reads two opaquely named values out of the page, mixes them with device signals, and posts. The cookie it returns is separate from _abck.*

The pixel challenge does not run everywhere. It appears on a subset of protected pages, and whether it fires is a configuration choice on the customer side rather than a constant of the system. This is exactly the property that makes it a trap for a client modelled on _abck alone. On a site that does not use the pixel challenge, you will never see ak_bmsc do anything load-bearing, and you might conclude it is decorative. Move to a site that does use it, and the same client starts getting incomplete-looking sessions because it is not minting the cookie the page expects. The behaviour did not change. The configuration did.

It is worth being precise about where the pixel challenge sits. There is no countdown and no 428, so it is not the proof-of-work interstitial. The signal bundle is smaller than the full handshake and the cookie it produces is ak_bmsc rather than _abck, so it is not the sensor step either. It is a third thing, sitting between the two, and that middle position is most of why it gets overlooked.

The signals the beacon collects are the ordinary furniture of browser fingerprinting. Community deobfuscations of the pixel script describe it gathering screen and window geometry, hardware hints, and the kind of math-and-device-property checks that distinguish a real browser engine from a headless or emulated one. The point of the pixel layer is not to collect everything the main sensor collects. It is to collect enough, cheaply, on pages where standing up the full sensor handshake would be overkill. A product page that mostly needs to confirm “is this a real browser at all” can run the pixel beacon and call it done, while a login or checkout path runs the full sensor and, when the score warrants, escalates to a challenge.

This is a useful place to note how Akamai differs from its closest competitor in shape. DataDome leans hard on a single JavaScript tag and a server-side scoring pipeline, with the client payload doing most of the collection in one place; the breakdown of that design is in the posts on DataDome’s detection model and its JS tag and ddjskey. Akamai instead spreads collection across distinct mechanisms with distinct cookies, so the same site can run a light beacon here and a heavy sensor there. The trade-off is that Akamai’s surface is more configurable per path and, for the same reason, harder to model as one thing. A client that learned DataDome’s single-payload shape and assumed Akamai works the same way will under-handle exactly the middle layer described here.

The sec-cpt 428 challenge

The most visible second-layer mechanism is the one that throws an HTTP status code at you. When Bot Manager decides a request is suspicious but not clearly malicious, it can escalate to an active challenge instead of blocking outright. On API and JSON endpoints, that escalation shows up as a 428 Precondition Required response. The 428 code itself is ordinary HTTP, defined in RFC 6585 (Nottingham and Fielding, April 2012) to mean the origin requires the request to be conditional. Akamai reuses the code to mean something specific: you have tripped the sec-cpt challenge, and you must solve it before any further request on this path will be served.

sec-cpt is short for the challenge that produces a sec_cpt cookie. The challenge arrives in one of two shapes. On HTML responses it is an iframe element with id="sec-cpt-if", carrying a provider attribute, a base64-encoded challenge attribute, a data-duration, and a src pointing at a path under /_sec/cp_challenge/. On JSON responses the same data comes back as an object with sec-cp-challenge set to true. Reading the public SDK parsers, the JSON payload exposes fields including provider, token, nonce, difficulty, timestamp, chlg_duration, and branding_url_content. A representative decoded challenge looks like this:

{
"sec-cp-challenge": "true",
"provider": "crypto",
"branding_url_content": "/_sec/cp_challenge/crypto_message-4-3.htm",
"chlg_duration": 30,
"token": "AAQAAAAJ...",
"timestamp": 1713283747,
"nonce": "ebccdb479fcb92636fbc",
"difficulty": 15000,
"timeout": 1000,
"cpu": false
}

The provider field decides what kind of work the challenge demands. There are three. The crypto provider is a proof-of-work puzzle with a mandatory wait. The behavioral provider asks for sensor data, the same telemetry-collection step as the main handshake, with no proof-of-work. The adaptive provider combines both: it wants a proof-of-work answer and a sensor submission, and it carries a count field saying how many answers to produce. Crypto and adaptive carry the token, nonce, and difficulty triple; behavioral and adaptive carry branding_url_content. The split matters because it tells you what the edge is actually testing. A crypto challenge is testing whether you will burn CPU and wait. A behavioral challenge is testing your client-side telemetry. Adaptive tests both at once.

sec-cpt provider types crypto proof-of-work + forced wait token, nonce, difficulty, chlg_duration behavioral sensor data no PoW branding_ url_content, verify_url adaptive both, in sequence crypto fields + branding + count *One status code, three different tests. The provider field tells you whether the edge wants CPU, telemetry, or both.*

The crypto proof-of-work is the part with teeth, and it is worth describing precisely because the mechanism is what makes it effective rather than any secrecy about it. The challenge gives you a nonce, a difficulty, and a timestamp. The client searches for an answer value such that the SHA-256 hash of a string built from those inputs plus the answer satisfies a condition derived from difficulty. Reading the public Python SDK, the hashed string is constructed in the shape sec followed by the timestamp, nonce, difficulty, and candidate answer concatenated together; the implementation walks the bytes of the resulting digest, applies bitwise and modulo operations against the difficulty, and accepts an answer when the computed output reaches zero. That is a classic hashcash-style partial-hash search. You cannot precompute it because the nonce and timestamp are per-challenge, and you cannot parallelise your way out of the cost cheaply because the difficulty sets how many candidates you expect to try.

Then there is the wait. The chlg_duration field is a number of seconds, and it is not advisory. The challenge is solved in two parts: you compute the proof-of-work answers, and you wait out the duration, and only then do you POST the result. The public SDKs expose this as an explicit sleep step keyed off the duration. Akamai’s own description of the crypto challenge, in a 2023 account of defending a bank against account-takeover bots, calls it a puzzle resolved entirely by the device rather than the user that forces bots to spend CPU cycles, and the company is candid about the goal: drive the cost and time per request up far enough that bot operators give up or run at a loss while real browsers see only a short countdown. The wait is the lever. A human waiting five seconds for a page is a minor annoyance. A scraper that must wait out a server-enforced duration on every challenged request has its throughput capped no matter how much hardware it throws at the hash.

The economics here are the entire design, so they are worth spelling out. A proof-of-work alone is a CPU tax, and a determined operator can pay it by renting more cores; the cost scales with hash rate, which is buyable. Pairing the proof-of-work with a server-enforced minimum wait changes the shape of the cost. The wait is wall-clock time the server insists on, and no amount of hardware shortens it, so the effective ceiling on requests-per-second-per-session is set by the duration rather than by how fast you can hash. To raise throughput past that ceiling, an operator has to run many sessions in parallel, and each session is its own bm_sz, its own sensor, its own cookie state to keep coherent. The challenge does not try to make automation impossible. It tries to make it expensive enough, per unit of successful traffic, that the people who care about a small advantage stop caring. That is the same logic behind the proof-of-work interstitials on other anti-bot and queueing systems; the field guide on bypassing Queue-it covers the queue-token variant of the same idea, where the friction is a wait rather than a hash but the goal of throttling per-session throughput is identical.

It also matters that cpu and timeout appear in the challenge JSON. The cpu flag and the timeout value give Akamai room to tune how hard the proof-of-work bites on a given client class without changing the protocol, and a difficulty in the tens of thousands sets the expected number of hash attempts. None of these are secret, and that is the point worth holding onto: the security of the crypto challenge does not come from hiding the algorithm. It comes from the fact that the work and the wait are real and per-challenge, so knowing exactly how the SHA-256 search works does not let you skip it.

The solved payload goes back as a POST to a verification path under /_sec/, with the public SDKs targeting /_sec/verify parameterised by provider, and the crypto and adaptive flows routing through /_sec/cp_challenge/verify. A challenge solved correctly produces a sec_cpt cookie, and the SDKs note that a fully satisfied challenge leaves the cookie value carrying a ~3~ segment, the same tilde-delimited marker style Akamai uses across its cookie family. Carry that cookie and the path stops returning 428. Lose it, or let it expire, and the 428 comes back. The interstitial is stateful, and the state lives in sec_cpt.

sec-cpt challenge as a state machine request 428 challenge served solve PoW + wait duration POST /_sec/ verify path cleared sec_cpt ~3~ Wrong answer or skipped wait loops back to a fresh 428. *The interstitial is stateful. The path stays gated until a verified solution mints a sec_cpt cookie carrying the ~3~ marker.*

One detail that catches clients out: the crypto challenge needs bm_sz and _abck present to generate the payload for the adaptive and behavioral cases, because those cases fold in a sensor submission, and the sensor is bound to bm_sz exactly as described earlier. So the layers are not independent in the way a clean diagram suggests. A sec-cpt adaptive challenge reaches back into the first layer for its telemetry, which means a client that mishandled bm_sz upstream will fail the interstitial downstream for a reason that looks unrelated. For how the score that triggers a 428 gets computed in the first place, the companion post on Akamai Bot Manager scoring traces the path from sensor collection to the decision; the 428 is what the decision looks like when it lands on “challenge” rather than “allow” or “deny”.

Why the layers are separate, and what that buys Akamai

Some of this shape is historical. The detection core inside Bot Manager came from Cyberfend, the startup Akamai bought in December 2016, whose BotFender product combined behavioural analysis with machine learning to spot automation in real time. Akamai folded that team and its models into Bot Manager and kept extending them against a far larger pool of traffic than a standalone startup could see. The layered structure that resulted is what you get when a behavioural detection engine acquired off the shelf is wired into an existing edge platform that already had its own cookie and challenge machinery. The cookies accreted; the challenge types were added as the score thresholds got more granular. It was assembled over years, not designed in one pass, and the seams are visible in exactly the place client authors trip on them.

It would be simpler, from a client author’s perspective, if Akamai folded all of this into one cookie and one handshake. It does not, and the separation is deliberate. Each layer tests a different thing and can be turned on independently per customer and per path.

bm_sz is the seed and the first-request assessment, set before any script runs, so it is the one piece of state that exists even for a client that never executes JavaScript. The pixel challenge is a cheap fingerprinting beacon that mints ak_bmsc and can run on pages where the full sensor handshake is not wanted. The sec-cpt interstitial is the expensive, visible escalation: a proof-of-work and forced wait that only fires when the score crosses a threshold, because you cannot put a five-second countdown in front of every visitor without driving them away. Layering lets Akamai apply exactly as much friction as a given request seems to warrant, from a silent cookie to a CPU-burning timer, and to reconfigure that per site without shipping new client code.

For anyone modelling the system from the outside, the practical consequence is that “handle _abck” is a description of one layer presented as if it were the whole. A client that validates _abck perfectly and ignores the rest will work flawlessly right up until it meets a site configured with the pixel challenge, or a path that escalates to sec-cpt, and then it will fail in ways that do not point back at _abck at all. The failure is a 428 with a countdown, or a session that looks complete but is missing ak_bmsc, or a sensor rejected because the bm_sz it was bound to went stale. These are the symptoms people describe as Akamai being inconsistent or random. It is not random. It is three layers with three triggers, and the one you instrumented happened to be quiet.

The cleanest way to hold the whole picture is to stop thinking of Akamai Bot Manager as a cookie to mint and start thinking of it as a graded response. The grade is set by the score, the score draws on signals from the very first request onward, and the response ranges from a passive cookie you barely notice to a proof-of-work timer you cannot rush. bm_sz is where the grading starts. The sec-cpt 428 is what the top of the scale looks like. Everything in between, the pixel beacon included, is Akamai deciding how hard to make you work for a page it is not sure you should have. The clients that get caught are the ones that learned the bottom of the scale and assumed it was the ceiling.


Sources & further reading

  • IETF (2012), RFC 6585: Additional HTTP Status Codes — defines 428 Precondition Required, the status code Akamai reuses for the sec-cpt challenge.
  • Hyper Solutions (2024), Handling 428 Status Code (SEC-CPT) — documents the three providers (crypto, behavioral, adaptive), the challenge fields, the forced wait, and the verification endpoints.
  • Hyper Solutions (2024), akamai package — pkg.go.dev — Go SDK reference listing the pixel and sec-cpt parsers and the SecCptChallenge struct fields.
  • Hyper Solutions (2024), hyper-sdk-py sec_cpt.py — Python source showing the SHA-256 proof-of-work construction and the challenge field names.
  • Hyper Solutions (2024), Akamai API reference — documents the pixel endpoint inputs, the bazadebezolkohpepadr html var, and the bm_sz pairing with _abck for sensor generation.
  • cookie.is (2026), bm_sz cookie — catalogue entry recording the ~4-hour duration and Akamai bot-management classification.
  • cookie.is (2026), ak_bmsc cookie — catalogue entry recording the ~2-hour duration and its bot-detection plus caching role.
  • Gao and Hickinbotham, Akamai (2023), Conquering Adversarial Bots and Humans to Prevent Account Takeovers — Akamai’s account of the crypto challenge as a device-resolved, cost-raising proof-of-work.
  • Akamai (2016), Akamai Acquires Cyberfend — the December 2016 acquisition that brought the BotFender detection technology into Bot Manager.
  • Akamai (2012), Challenge action — Terraform provider docs — official definition of a challenge action as a step that must be completed before a request is processed.
  • LincolnKermit (2024), Unofficial Akamai Bot Manager Wiki — community reference cataloguing the Akamai cookie family and pointing at deobfuscated sensor scripts.

Further reading