The DataDome cookie lifecycle: token issuance, rotation, and validation
Open the network panel on a site behind DataDome and the first thing you’ll see, before any product HTML loads, is a Set-Cookie: datadome=... header. It looks like any other session cookie. It is not. That single value is the carrier for a decision the protection layer already made about you, and on every subsequent request the edge re-reads it, re-checks it, and decides whether to forward you to the origin or hand you a challenge instead.
So what is actually in that cookie, and what makes it valid on one request and worthless on the next? The vendor calls it 128 bytes of encrypted data with no PII, which tells you almost nothing about the mechanics. This post stays in the cookie lane. We’ll trace where the datadome token comes from, what we can and cannot say about its contents, the events that cause it to rotate, how long it survives, and what the server actually does when it sees it. The detection signals that feed the decision, the JS tag that collects them, and the scoring pipeline that weighs them each get their own treatment elsewhere; here the subject is the token itself.
A map of the moving parts
Three pieces of DataDome infrastructure touch the cookie, and keeping them separate is the only way the lifecycle makes sense.
The first is the edge module. This is the integration that runs in front of the customer’s application, as an Nginx module, a CDN worker, a server-side SDK, or a reverse proxy. It sees every request, extracts the identity, and asks DataDome whether to let it through.
The second is the Protection API, reachable at api.datadome.co/validate-request/. The module sends it request metadata and gets back a verdict. The API is also where the authoritative Set-Cookie originates: when DataDome wants the client to carry a new or refreshed token, it tells the module to emit one.
The third is the client-side challenge stack, served from geo.captcha-delivery.com and ct.captcha-delivery.com, plus the JS tag loaded from DataDome’s api-js host. This is what runs when the verdict is “challenge.” It collects signals in the browser, the user solves something (or Device Check solves it silently), and a fresh, blessed datadome cookie comes back.
The cookie is the thread that stitches these together. A client earns one from the challenge stack, presents it to the edge module on later requests, and the Protection API decides on each request whether that thread is still good.
*The edge module reads the cookie and consults the Protection API; when the verdict is "challenge," the client-side stack mints a fresh token and the loop closes.*What the token looks like on the wire
Strip away the attributes and a real DataDome cookie is a short opaque string. A representative value captured from a live response looks like this:
Set-Cookie: datadome=Tdx_AVi.VpcPns7JD7n9~EedCazO2jmhdrv_5Hhxmg3ZnUB4iHxn1OE0pum84C2RrSAm_Tnbf7VfF-6.Kfy_XQGeYZBFPwQkbn2~xSmO0J; Max-Age=31536000; Domain=.example.com; Path=/; Secure; SameSite=LaxA few things are worth reading off directly. The alphabet is URL-safe base64-ish with ., ~, _, and - used as separators or padding, which is consistent with the value being an encoded, encrypted blob rather than anything you can decode by eye. DataDome’s own documentation puts the size at 128 bytes and states the cookie carries no personally identifiable information. The Max-Age=31536000 is exactly one year in seconds, which matches the documented one-year expiration. Secure means it only rides HTTPS. SameSite=Lax lets it ride top-level navigations but not most cross-site subrequests.
One attribute is conspicuously absent, and its absence is deliberate: there is no HttpOnly. The cookie has to be readable by JavaScript, because the JS tag and the challenge scripts both need to read the current token to build their payloads and write the refreshed token back. DataDome’s docs explicitly warn against adding HttpOnly to the cookie or changing its Domain and Path, because doing so breaks the client-side half of the system. That single missing flag is the structural reason the cookie can rotate from inside the page at all.
What’s inside the 128 bytes is not publicly documented. DataDome describes it only as encrypted data, and nobody outside the company has published a verified field layout. What can be said with confidence, from observed behavior rather than a leaked spec, is that the token has to encode enough to let the server tie a later request back to the session and decision that minted it. It is a reference into DataDome’s own state, sealed so the client can carry it but not forge it. Treat any byte-by-byte breakdown you find online as inference, not gospel.
*The attributes are ordinary; the missing HttpOnly flag is the load-bearing detail, because the client-side scripts have to read and rewrite the token.*For the question of what data DataDome actually harvests on that very first request, before any cookie exists, the companion piece on DataDome’s detection model walks the full signal list.
Issuance: how a client earns its first token
A fresh client has no datadome cookie. The first request arrives bare. From DataDome’s point of view this is the most interesting moment, because the only evidence available is the HTTP request itself: the IP and its reputation, the TLS handshake and its JA3 or JA4 fingerprint, the header order, the User-Agent against the client hints. There’s a whole post on how the server-side scoring pipeline turns those into a number; here we care only about what comes out the other side as a token.
Two outcomes issue a cookie. In the clean case, the first-request signals look human enough, the verdict is allow, and the Protection API instructs the edge module to set a datadome cookie straight away. The client now carries a token it never had to work for. In the suspicious case, the verdict is challenge, the origin response is replaced by a 403 that loads the challenge stack, and the client only earns a usable token by completing whatever runs there.
The challenge response is recognizable. It comes back with a 403 status and a body that bootstraps a script from the captcha-delivery hosts: ct.captcha-delivery.com/i.js for the interstitial flavor, ct.captcha-delivery.com/c.js for the slider. Embedded in the page is a JavaScript dd object that carries the parameters the challenge needs. Observed fields include cid (a challenge or client identifier), hsh (a hash), host, and a small set of single-letter keys such as s, b, e, and t whose exact meaning depends on the challenge type. The t field is the most legible from the outside: t=fe is a standard interactive challenge, and t=bv signals a hard block where the source is banned outright and solving the challenge changes nothing.
The challenge script then collects its client-side signals, the user solves the puzzle (or Device Check completes its silent proof of work), and the result posts back to DataDome. If the verification passes, the response carries a brand-new datadome cookie. That is the moment of issuance for the contested path. The token the client now holds is the receipt for a passed challenge, and on the next request to the origin the edge module will read it, validate it, and let the request through.
Device Check deserves a note here because it changed the issuance picture. DataDome launched it on 12 December 2023 as an invisible response challenge. Instead of showing a puzzle, it runs a JavaScript proof of work and collects client-side signals (screen metrics, codecs, canvas rendering, execution timing) on an interstitial page, then posts the result back for a verdict. Co-founder and CEO Benjamin Fabre framed the goal as reducing how often a visible CAPTCHA is needed at all. The company says fewer than 1 in 10,000 human requests get challenged, and decisions run across its points of presence in milliseconds. For the cookie, the consequence is that a token can now be minted without a human ever clicking anything. The interstitial sets the cookie once the silent check passes, and the user notices nothing but a brief delay.
What the JS tag and the cookie pass between them
The cookie does not work alone. On a normally browsing client, the DataDome JS tag runs in the page, reads the current datadome value, and feeds telemetry back to DataDome out of band. The details of that payload, including the ddjskey that identifies the customer and the fields the client sends, are covered in the piece on the DataDome JS tag. For the lifecycle, the part that matters is the feedback loop: the tag can prompt DataDome to refresh the token mid-session, and because the cookie isn’t HttpOnly, the new value can be written from script without a full challenge round trip.
This is also why DataDome keeps a few satellite values alongside the main cookie. A transient dd_testcookie gets written and immediately deleted to confirm the client can store cookies at all. When the sessionByHeader option is on, a ddSession entry mirrors the token into Local Storage so the session survives even where cookie handling is unreliable. A ddOriginalReferrer is parked in Session Storage to remember where the user was headed before a challenge interrupted them. None of these is the token; they exist to keep the token’s session intact across the rough edges of real browsers. The docs are blunt that blocking, tampering with, or deleting any of them invites challenge loops.
The mobile path: when the cookie becomes a header
Everything above assumes a browser with a cookie jar. Native mobile apps don’t have one in the same sense, and DataDome handles them through a parallel mechanism that’s worth understanding because it changes where the token lives without changing what it is.
On mobile, the DataDome SDK manages the identifier itself. Instead of relying on a Set-Cookie round trip through a browser cookie store, the SDK can present the token as the X-DataDome-ClientID request header. The Protection API’s documented precedence makes this explicit: if X-DataDome-ClientID is present, the module uses it as the ClientID and ignores the cookie; only when the header is absent does it fall back to the datadome cookie. The two are interchangeable as the session identifier, which is the whole point. A token minted in one form can be carried in the other.
There’s a small protocol wrinkle on the response side. When the module reads the ClientID from the header rather than the cookie, it signals that to DataDome by adding X-DataDome-X-Set-Cookie: true to its API call. DataDome then returns the refreshed token as X-Set-Cookie instead of a standard Set-Cookie, so the SDK can pick it up and store it in app-managed storage rather than expecting a browser to handle a cookie it has no jar for. On iOS specifically, DataDome’s guidance is to keep the token in the shared HTTPCookieStorage and to avoid ephemeral URL sessions or manually appended Cookie headers, because either one can silently drop the token and tip the app into a challenge loop. The lesson is the same as the browser case from a different angle: the token has to persist somewhere the SDK controls, and the storage medium is an implementation detail, while the binding to the session is not.
This is also a reminder that “cookie” is shorthand. The datadome value is a session token that usually travels as a cookie but can travel as a header, and DataDome’s own integration logic treats those two carriers as one logical field. Anyone reasoning about the lifecycle at the HTTP layer should hold the abstraction at “token,” with cookie and header as two of its skins.
Rotation: the events that retire a token
A datadome cookie is not a static credential you obtain once and reuse for a year. The one-year Max-Age is a ceiling, not a guarantee. In practice the token rotates, and rotation is the part of the lifecycle most often misread.
Two different things get called “rotation,” and they’re worth separating. The first is reissuance: DataDome decides to hand the client a new token and emits a fresh Set-Cookie, replacing the old value. This can happen on an allow verdict as a matter of course, after a passed challenge, or when the JS tag’s telemetry prompts a refresh. The client simply starts carrying the new string. The second is invalidation: the token the client holds stops being accepted, not because a new one was issued, but because the context the token was bound to no longer matches.
That binding is the key. Independent observation consistently reports that the token is evaluated against the IP and the fingerprint that were present when it was minted. Change the IP, switch browsers, or alter the fingerprint, and the cookie that was valid a moment ago is treated as stale, which trips a fresh challenge. The cookie is not a bearer token you can lift from one client and replay from another and expect to work. It is bound to the session that earned it, and DataDome’s validation step is where that binding gets enforced.
Several common events therefore retire a token in practice:
- A new challenge is solved, producing a replacement cookie that supersedes the old one.
- The source IP changes (a new proxy, a mobile network handoff, a VPN toggle), so the presented token no longer matches the context it was bound to.
- The client fingerprint shifts (a different browser, a spoofed but inconsistent User-Agent, a headless signature), again breaking the binding.
- DataDome’s own risk view of the session changes, and it elects to re-challenge regardless of the cookie’s nominal expiry.
The practical upshot for anyone operating an HTTP client (legitimately, against their own infrastructure or with permission) is that you cannot treat the datadome cookie as a portable session key. Pin the cookie to the exact IP and TLS-and-header fingerprint that obtained it, and it behaves. Move it and it dies. That coupling is intentional, and it’s the cookie-layer expression of a detection philosophy that distrusts any signal that can be copied in isolation. The way DataDome turns the network layer itself into a fingerprint that the cookie gets bound to is the subject of the HTTP/2 fingerprinting writeup.
Validation: what the edge actually does with the cookie
Here is where the cookie earns or loses its keep, and it’s the best-documented stage because the Protection API has a public reference. On every request, the edge module pulls the identity out of the request and POSTs a metadata bundle to api.datadome.co/validate-request/. The module does not decide anything itself. DataDome does, and the module enforces the verdict.
The identity field is ClientID, and it has a defined precedence. If the request carries an X-DataDome-ClientID header, that wins. Otherwise the module reads the datadome cookie value and uses that as the ClientID. The header path exists mainly for native and mobile clients that manage the identifier themselves; the cookie path is the default for browsers. When the header is used, the module is supposed to add X-DataDome-X-Set-Cookie: true to its API call so DataDome answers with an X-Set-Cookie it can relay rather than a raw Set-Cookie.
Around that identifier, the module ships a large bundle of request metadata. The documented payload includes the obvious HTTP fields (IP, TrueClientIP, XForwardedForIP, Host, Method, Protocol, UserAgent, Referer, the request URI), header-level fields like HeadersList, CookiesList, CookiesLen, and notably the TLS fingerprint fields JA3, JA4, TlsProtocol, and TlsCipher. Client-hint headers (SecCHUA, SecCHUAPlatform, SecCHUAMobile) ride along too. The whole payload is URL-encoded with a 24 kB ceiling, and individual fields are truncated to documented limits. The datadome cookie’s value is one field among many, but it is the one that lets DataDome look up the prior decision and the binding that goes with it.
The verdict comes back in the X-DataDomeResponse header, and the module branches on it. An allow verdict is a 200: forward the request to the origin, and if DataDome supplied a Set-Cookie, pass it through so the client’s token gets refreshed. A challenge verdict arrives as one of a fixed set of status codes (301, 302, 401, 403, or 429): the module returns DataDome’s own response to the client and never touches the origin. Anything else, or a timeout, triggers fail-open, where the module treats the request as allowed so a DataDome outage can’t take the customer’s site down with it.
Two response headers do the plumbing. X-DataDome-request-headers lists the headers the module must inject into the upstream request before forwarding, and X-DataDome-headers lists the headers to add to the downstream response, which is how the refreshed Set-Cookie reaches the client. The reference is firm that these pointer headers must never be forwarded to the end user as-is, a guardrail against leaking the internal protocol. There’s a deeper look at how the verdict itself is computed in the scoring pipeline post; the relevant point for the cookie is that the module’s job is mechanical. It reads the token, ships it, and obeys.
One subtlety in the validation path is integrity. The module is instructed to confirm that the HTTP status of DataDome’s response matches the decision named in X-DataDomeResponse; a mismatch is treated as a fault and triggers fail-open. This is a defense against a tampered or truncated API response being misread as an allow. The cookie’s own integrity is handled inside the opaque token rather than by the module, which has no key to inspect it. The module trusts DataDome to tell it whether the presented cookie was good.
It’s worth being precise about where the secret lives. The edge module never holds a key that would let it decrypt or validate the datadome value on its own. It cannot tell a forged token from a real one by inspection, and it doesn’t try. All of the cryptographic judgment happens inside DataDome’s infrastructure, behind the Protection API. The module’s entire relationship to the cookie is to extract it, attach it to a metadata bundle as the ClientID, send it off, and act on whatever verdict returns. That split matters for a defender reasoning about the threat surface: there is no client-side or customer-side secret to leak, because the customer’s integration is deliberately kept dumb. Compromising the cookie’s validation would mean compromising DataDome’s own backend, not the customer’s edge.
Reading the cookie’s state from the outside
Because the token is opaque, you can’t read its state from the value. You read it from the system’s reaction. A request that carries a token DataDome still honors gets a clean 200 and the origin’s real response, sometimes accompanied by a refreshed Set-Cookie. A request whose token has drifted out of its binding, or which never had one, gets a challenge: a 403 (most commonly) that loads the captcha-delivery script and carries the dd object described earlier. The t field inside that object is the most informative external tell. A t=fe says the system wants an interactive challenge solved, which a legitimate user can complete and walk away with a fresh token. A t=bv says the source is hard-blocked at a level the cookie can’t fix; solving the challenge produces nothing, because the verdict isn’t about the token at all but about the IP or network behind it.
This is the practical difference between a token that’s merely stale and a context that’s been condemned. A stale token is recoverable: pass the challenge from the same context and you get a new one. A condemned context is not, at least not from the cookie layer, because no token issued to that IP or fingerprint will be honored until whatever flagged it clears. Reading the 403 plus the t value together is how an operator tells those two situations apart without ever decoding a single byte of the cookie.
Where the cookie fits in the bigger detection model
It helps to be clear about what the cookie is not. It is not the detection. DataDome’s models reportedly process trillions of signals a day and return verdicts in single-digit milliseconds, and the bulk of that work happens on the request and on the client-side telemetry, not on the cookie. The cookie is the memory layer. It lets a decision made once persist across the next requests so the heavy evaluation doesn’t have to run from scratch every time, and it lets a passed challenge actually mean something on subsequent navigations.
That memory is also why losing the cookie is so disruptive. DataDome’s own iOS guidance spells out the failure mode: an app that doesn’t persist the cookie in the shared HTTPCookieStorage, or that uses ephemeral sessions, can fall into a challenge loop, where every request looks like a brand-new unproven client and the protection layer keeps re-challenging. The cookie is what tells the system “this client already proved itself a moment ago.” Drop it and you erase that proof on every hop.
There’s a forward-looking thread worth naming. DataDome collects request signatures and supports the HTTP Message Signatures mechanism from RFC 9421, published in February 2024, which defines the Signature and Signature-Input headers for signing components of an HTTP message. That standard points at a world where some of what a cookie does today (carrying a verifiable assertion about a client across requests) could move into signed headers that don’t depend on cookie storage at all. The datadome cookie isn’t going anywhere soon, but Device Check’s cookieless-leaning design and the interest in signed requests both suggest the token is one mechanism among a growing set, not the permanent center of the system.
The same pattern shows up across the industry. Akamai’s Bot Manager leans on its own _abck and bm_sz cookies with a comparable issue-validate-rotate loop, dissected in the _abck cookie and bm_sz pixel challenge posts. The shapes differ, but the idea is the same: a sealed token the client carries, bound to context the client can’t fake, re-checked on every request.
Closing: the cookie is a receipt, not a key
If there’s one thing to hold onto, it’s that the datadome cookie is a receipt for a decision, not a key that grants access. A key works wherever you present it. This token works only where its context still matches: the same IP, the same fingerprint, the same session DataDome remembers minting it for. The 128 opaque bytes are sealed precisely so the client can carry the receipt without being able to read or rewrite the decision inside it. The one-year Max-Age is almost a misdirection, since the binding to IP and fingerprint is what actually governs how long the token lives, and that can be seconds if the client moves.
Everything else in the lifecycle follows from that. Issuance is the act of recording a decision into a token. Rotation is either re-recording the same session’s decision or discarding a token whose context has drifted. Validation is the edge asking DataDome, on every single request, whether the receipt still corresponds to a decision worth honoring. The cookie never decides anything; it only remembers. Build a client that respects the binding and the cookie behaves predictably. Build one that treats it as a portable credential and you’ll watch it die on the first hop that changes the context it was sealed against.
Sources & further reading
- DataDome (2025), Cookies and stored data — vendor doc giving the
datadomecookie’s 128-byte size, one-year expiry, no-PII claim, and thedd_testcookie/ddSession/ddOriginalReferrersatellites. - DataDome (2025), Protection API: Validate Request — the authoritative request/response spec:
ClientIDprecedence,X-DataDomeResponse, allow/challenge/fail-open status codes, and the header-injection protocol. - DataDome (2025), DataDome cookie: best practices on iOS — explains cookie persistence via
HTTPCookieStorageand the challenge-loop failure mode when the token is lost. - DataDome (2025), Device Check — describes the invisible interstitial, the client-side signals it gathers, and how a verified outcome is remembered.
- DataDome (2023), DataDome launches Device Check — launch announcement with the proof-of-work design and the goal of reducing visible CAPTCHA.
- PR Newswire (2023), DataDome launches Device Check (press release) — the 12 December 2023 date, the 26 POPs, the <1-in-10,000 figure, and the Benjamin Fabre quote.
- Hyper Solutions (2025), DataDome: getting started — third-party documentation of the
captcha-delivery.comhosts, thei.js/c.jssplit, theddobject fields, thet=fe/t=bvsemantics, and the observed cookie attribute string. - campo (2024), How to detect, block and manage DataDome — a researcher writeup with a captured
datadome=value and thecid/hsh/t/hostfields parsed from a challenge page. - ProxyHat (2025), DataDome detection and how legitimate automation passes — observed behavior on the token’s IP-and-fingerprint binding and what invalidates it.
- Scrapfly (2026), How to bypass DataDome anti-scraping — notes the 403 challenge pattern and DataDome’s stated 5-trillion-signals-per-day, sub-2-millisecond decision scale.
- IETF (2024), RFC 9421: HTTP Message Signatures — defines the
SignatureandSignature-Inputheaders DataDome collects, pointing at a cookie-adjacent future for verifiable client assertions.
Further reading
DataDome'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 readDataDome's server-side scoring pipeline: from edge to decision in milliseconds
Traces how DataDome turns an HTTP request into an allow, challenge, or block verdict at the edge: the module-to-API split, the form fields it ships, the regional inference layer, and the latency budget that keeps it synchronous.
·22 min readAkamai's bm_sz cookie and pixel challenge: the second layer most clients miss
Traces the bm_sz cookie, the pixel challenge that mints ak_bmsc, and the sec-cpt proof-of-work interstitial that sits alongside _abck, and why a client that only validates _abck still gets challenged or dropped.
·22 min read