The Web Scraping Club

The Web Scraping Club

The LAB #115: Manus at Work - Datadome on Leboncoin.fr

What DataDome's tag really sends and collects, read line by line on leboncoin.fr.

Pierluigi Vinciguerra's avatar
Pierluigi Vinciguerra
Sep 10, 2026
∙ Paid

Let’s continue the series of articles where we use Manus to better understand the surface of anti-bot solutions. The last two times, it was for THE LAB #108 for Akamai on Net-a-Porter, and in THE LAB #114 it was Kasada on nike.com.

Everyone who scrapes a site protected by DataDome knows the basic theory. The tag reads the browser fingerprint, then it watches how you use the page, and sends the result to a server that gives you a score. You can read about that online almost everywhere, even on this Substack. This time I wanted to go a bit deeper and read what the tag really does. I used Manus for the first analysis of the code, and then I checked every claim using Claude.

Give your AI a web data layer – Decodo’s Web Scraping API turns any site into clean, structured data your models can actually use.

Activate starter plan

In THE LAB #114 I opened Kasada on nike.com and spent most of the article on its interpreter. Two custom virtual machines, 279 opcodes in total, and a decoder based on the clock, so the file stops working after fifteen hours.

DataDome is the other name you meet all the time, so I expected something similar. It’s different. The sensor is 124 KB of minified JavaScript, the method names are still readable, and the string table is plain base64. This difference is the most interesting thing I found, so at the end I put the two products side by side.

The target is leboncoin.fr, the French classifieds site. It runs DataDome JavaScript Tag 5.9.4.

How DataDome is installed on leboncoin

There are five different pieces, and when you say “DataDome blocked me” you should know which one you mean.

A remote component in front of the application answers a navigation with the real page or with a 403 that contains the challenge bootstrap. dd.leboncoin.fr/tags.js is the sensor. It’s hosted on S3 behind CloudFront, on a hostname that looks like part of leboncoin (the response headers say server: AmazonS3 and via: cloudfront.net). dd.leboncoin.fr/js/ receives the data the sensor collects. ct.captcha-delivery.com serves the two bootstraps, i.js for the device check and c.js for the CAPTCHA. geo.captcha-delivery.com serves the page the user sees.

The sensor is the piece I want to study, because it’s the only one where I can read the code. The server-side scoring is out of scope, for the simple reason that nobody outside DataDome can see it.

We wrote about DataDome many times, in THE LAB #2, in the cookie work of THE LAB #94 and in the agentic test of THE LAB #103. In THE LAB #106 we also saw which properties DataDome reads, from inside the browser engine, with camoufox-reverse. All those articles asked if we could pass, or what the browser layer shows, but this time the focus is on opening the tag file and seeing what’s inside.


When CAPTCHAs interrupt a workflow, reliable infrastructure matters. anyIP provides residential and mobile proxies with consistent sessions for smoother web-data collection.

Start Scraping with AnyIP


Method

I used two tools, Camoufox and Manus, and the article is the result of the two approaches. On one side, the runs and the report of Manus. On the other side, the check of every claim of that report, done by Claude on the bytes and on the wire.

I launched the first pass with Manus on Le Bon Coin, similarly to what I used for Kasada in #114. The challenge path and the fingerprint inventory in this article are its results, on several runs. I take these runs as they are. Manus worked with a stateful Chromium session, some direct curl requests and static analysis of the files served by the site. No external sources, no stealth tools.

Its outcome matrix has eleven samples. The home page loaded with a 200 and the tag loaded with it. A HEAD and a fetch GET to the same page, sent from that same session, both got a 403. Four curl requests to the home page and to the ad page, with and without a Chrome User-Agent, all got a 403 with a challenge bootstrap of 771 to 774 bytes. The User-Agent only changed the bootstrap type, rt=c or rt=i. The navigation to the ad page got a 403 and a device check. After the human check, the frame became a CAPTCHA, then “Access is temporarily restricted”, and the block followed the session back to the home page. The ad page never arrived.

Then Manus reviewed its own report, with three blind audits on separate parts of the evidence and one adversarial pass that tried to prove each claim false. When the evidence was not enough for a claim, it lowered the claim. The first one was if the CAPTCHA was ever accepted by the server. This is the material I started from. Camoufox was admitted where the Chromium of Manus was not, so the numbers from the wire below, the POST body, and the live x-dd-b header, come from my captures.

My collection was read-only. I didn’t replace native APIs, I didn’t modify requests, I didn’t replay tokens and I didn’t decrypt payloads. Three captures with the same profile and the same target, with cache busting between them, from a residential connection in Italy. The tag didn’t change between the Manus run and mine, so both of us read the same 123,989 bytes, SHA-256 16a5a027...3151.

Every claim has an evidence level, and there are only three:

  • TABLE means the string is in the decoded string table and nothing more

  • SOURCE means the identifier is in a place where the code can execute it

  • WIRE means I saw it in a request or a response I captured

A string in the table has not been read by anything. A property that is read has not necessarily been sent. Keeping these three levels separate is most of the work in this kind of analysis, and I lost track of it more than once while reading the string table.

The claim table that puts all of this together is results/verification.md, available in the same directory of The Lab repository, available for paying users.


Start your scraping journey with Byteful: 10GB New Customer Trial | Use TWSC for 15% OFF | $1.75/GB Residential Data | ISP Proxies in 15+ Countries

Claim your 10GB here


What the tag sends

The sensor sends one POST to dd.leboncoin.fr/js/, from the main frame, with no query string. I have two of these bodies, 5,069 and 4,996 bytes of form-urlencoded data, and both have the same nine fields in the same order. This is the second one, as recorded by capture_hardblock_scope.py. The opaque values are replaced by their length and a hash prefix:

POST https://dd.leboncoin.fr/js/   frame: https://www.leboncoin.fr/   body: 4996 bytes
jspl           len 4698   sha256[:12] df708add1476
eventCounters  []
jsType         ch
cid            len 128    sha256[:12] db1e75da8bbc
ddk            len 30     sha256[:12] d1cd9afe92aa
Referer        https%3A%2F%2Fwww.leboncoin.fr%2F
request        %2F
responsePage   origin
ddv            5.9.4

Four of these fields are worth a look. ddv is 5.9.4, so the version is in the payload and not only in the file banner. jsType was ch, the challenge collection, while the initial one has a different value. responsePage was origin. eventCounters was [], empty, because a headless run with no real interaction has no behaviour data to send. That empty array is what my client looked like from the other side.

jspl is the real signal payload, 4,771 characters in one capture and 4,698 in the other, and it stays opaque. cid was 128 characters, the same length of the datadome cookie. The cookie is readable from JavaScript, with Secure and SameSite=Lax, on the domain .leboncoin.fr. ddk is 30 hex characters and it was identical in all captures, so it’s the client key of the site and it has nothing to do with the session.

There is also a diagnostic sample. When Math.random() <= .05 (line 2937 of the beautified tag), the tag serializes window.ddoptions in a field called opts and window.ddCaptchaOptions in xhr_opts. One request in twenty sends the DataDome configuration of the site back to DataDome.


Check the TWSC YouTube Channel


What the fingerprint is made of

jspl is opaque on the wire, but the code that builds it is only partly obfuscated, and this is where the Manus report is most useful. The report divides the collection in two classes, the environment fingerprint and the behaviour telemetry. For the fingerprint, it groups the probes in families. I keep the same grouping here, and I checked every name below in the decoded string table and in the beautified source with Claude. If I don’t say otherwise, these are TABLE level: the tag has the probe, but from the bytes I can’t show which branch runs it in a given session.

User's avatar

Continue reading this post for free, courtesy of Pierluigi Vinciguerra.

Or purchase a paid subscription.
© 2026 The Web Scraping Club SRL · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture