cdpfleet Docs Sign up free

Examples & benchmarks / #84 · 2026-11-06 · Benchmark · Medium · for web scraping

Does the TLS fingerprint (JA4) explain which browsers got in? Twelve browsers against six vendors

Each of the twelve browsers from the real-site lab week loads a TLS echo page once, so we know the JA4, peetprint and HTTP/2 fingerprint it sends. Grouped by identical JA4, their recorded outcomes against Cloudflare, DataDome, Akamai, HUMAN, Kasada and AWS WAF (2026-10-26..30) show whether the fingerprint lines up with who got the page. Fingerprints measured 2026-11-06.

Google Chrome Microsoft Edge Brave Opera Yandex Browser Naver Whale Chromium Patchright Firefox WebKit Camoufox

Run on production on 2026-11-06: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

A benchmark: the results below are what this setup measured on the dates shown, on our own test runs. Sites, networks and browsers change, so rerun the code before you rely on the numbers.

The problem

A common explanation for "this browser gets blocked and that one doesn't" is the TLS fingerprint: the ClientHello a browser sends, summarised as a JA4 string, plus how it opens HTTP/2. Our real-site lab week (2026-10-26 to 10-30) recorded which of twelve browsers got the page behind Cloudflare, DataDome, Akamai, HUMAN, Kasada and AWS WAF on 16 real sites. This case measures the fingerprint each of those twelve setups sends and lines it up with those recorded outcomes: do browsers that send the same JA4 get the same answer, and do different JA4s get different ones? It measures a correlation in one small sample and claims nothing beyond it.

What we used, and why

WhatWhy
The twelve setups of the lab weekChrome headful and headless, Edge, Brave, Opera, Yandex, Whale, headless Chromium and Patchright (all Chromium-based), Firefox, WebKit and Camoufox (based on Firefox source; not affiliated with Mozilla), launched exactly as in cases 70-77. The returning Camoufox profile of those cases is the same Camoufox build and isn't measured or compared separately.
tls.peet.ws/api/allA TLS echo page: it answers with the JA4, the peetprint (a fuller TLS fingerprint) and the Akamai-format HTTP/2 fingerprint of the connection it received. The browser loads it itself with page.goto() — Playwright's own request client would send a different fingerprint. One load per setup. If it doesn't answer (it has had outages), tls.browserleaks.com/json is loaded instead (JA4 and HTTP/2, no peetprint); every published run got its answer from tls.peet.ws. (In one unpublished Java run tls.peet.ws didn't answer Camoufox; tls.browserleaks.com returned the same JA4.)
proxy: cdpfleet-resi-country-us-session-<id>One sticky residential exit for the whole run, one session at a time, like the lab week. A proxy tunnel passes the browser's own TLS handshake through; our per-version snapshots, measured through a different proxy, got the same fingerprints.
The lab week's stored resultsNot re-measured: the counts come from the five runs (one per language) stored in cases 70, 71, 73, 75 and 77, counted like the scoreboard: got in = the real page, or an automatic check that cleared by itself; network errors left out.
Our per-version snapshotsEvery browser build is measured once on tls.peet.ws when it first appears on the fleet. They show which fingerprint each setup's build sent during the lab week, so a browser update between then and today can't be mistaken for a difference.

How it works

  1. For each of the twelve setups, one after another: launch it through the sticky residential exit, open a new context, load the TLS echo page once and keep its JA4, peetprint and HTTP/2 fingerprint with the browser build.
  2. Print every setup's fingerprint.
  3. Group the setups by the JA4 they sent in the lab week (today's, checked against the week's snapshots; Yandex by its snapshot, see below).
  4. Per vendor and per site, count how often the members of each group got in, and list the pairs with the identical JA4, peetprint and HTTP/2 fingerprint where one got in on every load and the other on none.

The code

The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:

// npm install [email protected]
// env: CDPFLEET_API_KEY
import { randomBytes } from 'node:crypto';
import { chromium, firefox, webkit } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;
const RUN = randomBytes(3).toString('hex');
// The same twelve setups as the real-site lab cases 70-77 (Rebrowser needs a 1.52 client, so it's left out).
const SETUPS = [
  ['chrome_headful', 'Chrome, headful', 'chrome', chromium, false],
  ['chrome_headless', 'Chrome, headless', 'chrome', chromium, 'new'],
  ['edge', 'Edge, headful', 'edge', chromium, false],
  ['brave', 'Brave, headful', 'brave', chromium, false],
  ['opera', 'Opera, headful', 'opera', chromium, false],
  ['yandex', 'Yandex, headful', 'yandex', chromium, false],
  ['whale', 'Whale, headful', 'whale', chromium, false],
  ['chromium_headless', 'Chromium, headless', 'chromium', chromium, true],
  ['patchright', 'Patchright, headful', 'patchright', chromium, false],
  ['firefox', 'Firefox, headful', 'firefox', firefox, false],
  ['webkit', 'WebKit, headful', 'webkit', webkit, false],
  ['camoufox', 'Camoufox, headful', 'camoufox', firefox, false],
];

async function launch(engine, headless, proxy) {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(`https://starter.cdpfleet.com/${engine}/session`, {
      method: 'POST',
      headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
      body: JSON.stringify({ proxy, headless, record: false }),
    });
    if (res.ok) return (await res.json()).wsUrl;
    if (![429, 503].includes(res.status) || attempt === 5) throw new Error(`launch ${res.status} ${await res.text()}`);
    await new Promise((r) => setTimeout(r, (Number(res.headers.get('retry-after')) || 5) * 1000));
  }
}

// The browser itself loads a TLS echo page, which answers with the ClientHello and HTTP/2 frames
// it received from this very browser. tls.peet.ws first; tls.browserleaks.com/json only if peet
// doesn't answer (it has had outages). Each is one page load in a fresh tab.
const ECHOES = [
  ['tls.peet.ws', 'https://tls.peet.ws/api/all', (j) => ({ http_version: j.http_version, ja4: j.tls?.ja4, ja4_r: j.tls?.ja4_r ?? null, peetprint_hash: j.tls?.peetprint_hash ?? null, akamai_h2: j.http2?.akamai_fingerprint ?? null, akamai_h2_hash: j.http2?.akamai_fingerprint_hash ?? null, user_agent: j.user_agent })],
  ['tls.browserleaks.com', 'https://tls.browserleaks.com/json', (j) => ({ http_version: null, ja4: j.ja4, ja4_r: j.ja4_r || null, peetprint_hash: null, akamai_h2: j.akamai_text || null, akamai_h2_hash: j.akamai_hash || null, user_agent: j.user_agent })],
];

async function fingerprint(browser) {
  const context = await browser.newContext();
  let last = null;
  for (const [source, url, read] of ECHOES) {
    const tab = await context.newPage();
    try {
      const res = await tab.goto(url, { waitUntil: 'domcontentloaded', timeout: 30000 });
      const fp = read(JSON.parse(await res.text()));
      if (fp.ja4) return { source, ...fp };
      last = `${source}: no ja4 in the answer`;
    } catch (err) {
      last = `${source}: ${err.message.split('\n')[0]}`;
    } finally {
      await tab.close().catch(() => {});
    }
  }
  throw new Error(last);
}

// One sticky residential exit for the whole run, and one session at a time.
const proxy = `cdpfleet-resi-country-us-session-c84${RUN}-lifetime-30`;
const fingerprints = {};
for (const [id, , engine, type, headless] of SETUPS) {
  try {
    const browser = await type.connect(await launch(engine, headless, proxy), { headers: { 'x-api-key': KEY } });
    try {
      fingerprints[id] = { version: browser.version(), ...(await fingerprint(browser)) };
    } finally {
      await browser.close();
    }
  } catch (err) {
    fingerprints[id] = { error: err.message.split('\n')[0].slice(0, 200) };
  }
}
console.log(JSON.stringify({ measured_at: new Date().toISOString().slice(0, 10), setups: Object.fromEntries(SETUPS.map((s) => [s[0], s[1]])), fingerprints }, null, 2));

What we got

Setup (JA4 group)browser.version() todayJA4 today (peetprint · HTTP/2)Cloudflare, got in (2026-10-26)DataDome, got in (2026-10-27)Akamai, got in (2026-10-28)HUMAN, got in (2026-10-29)Kasada, got in (2026-10-30)AWS WAF, got in (2026-10-30)
J1 · Chrome, headful157.0.8103.38t13d1517h2_8daaf6152771_cb7bf5808d99 (fc97c1cd · 52d84b11)14/1415/159/149/141/109/9
J1 · Chrome, headless157.0.8103.38t13d1517h2_8daaf6152771_cb7bf5808d99 (fc97c1cd · 52d84b11)5/154/145/155/150/105/10
J1 · Opera, headful155.0.8059.85t13d1517h2_8daaf6152771_cb7bf5808d99 (fc97c1cd · 52d84b11)15/1511/1515/158/150/1010/10
J1 · Yandex, headful26.10.0.0t13d1517h2_8daaf6152771_cb7bf5808d99 (fc97c1cd · 52d84b11)15/15 (as J2 that week)6/15 (as J2 that week)5/15 (as J2 that week)13/15 (as J2 that week)0/10 (build changed that day)10/10 (build changed that day)
J2 · Edge, headful156.0.4314.58t13d1516h2_8daaf6152771_806a8c22fdea (8f568b2a · 52d84b11)15/1511/1510/157/140/1010/10
J2 · Brave, headful156.0.8078.80t13d1516h2_8daaf6152771_806a8c22fdea (8f568b2a · 52d84b11)15/1510/1515/156/151/1010/10
J2 · Whale, headful150.0.7871.252t13d1516h2_8daaf6152771_806a8c22fdea (67c3e911 · 52d84b11)15/1511/1510/1514/150/1010/10
J3 · Chromium, headless148.0.7778.96t13d1516h2_8daaf6152771_d8a2da3f94cd (1d4ffe9b · 52d84b11)5/155/155/155/150/105/10
J3 · Patchright, headful148.0.7778.96t13d1516h2_8daaf6152771_d8a2da3f94cd (1d4ffe9b · 52d84b11)10/1510/1510/156/150/1010/10
J4 · Firefox, headful150.0.2t13d1617h2_86a278354501_3cbfd9057e0d (fd4547ee · 6ea73faa)10/1511/1510/155/150/109/10
J5 · Camoufox, headful156.0.1-beta.34t13d1517h2_8daaf6152771_3cbfd9057e0d (a4bbbc7e · 6ea73faa)15/157/1512/1513/156/1010/10
J6 · WebKit, headful26.4t13d2913h1_723694b0fccc_5671b5df5029 (e2a4f6ec · HTTP/1.1, no HTTP/2)5/158/1510/1512/150/108/10

From the Node.js run on 2026-11-06. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.

Raw output (Node.js)
{
  "measured_at": "2026-11-06",
  "setups": {
    "chrome_headful": "Chrome, headful",
    "chrome_headless": "Chrome, headless",
    "edge": "Edge, headful",
    "brave": "Brave, headful",
    "opera": "Opera, headful",
    "yandex": "Yandex, headful",
    "whale": "Whale, headful",
    "chromium_headless": "Chromium, headless",
    "patchright": "Patchright, headful",
    "firefox": "Firefox, headful",
    "webkit": "WebKit, headful",
    "camoufox": "Camoufox, headful"
  },
  "fingerprints": {
    "chrome_headful": {
      "version": "157.0.8103.38",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1517h2_8daaf6152771_cb7bf5808d99",
      "ja4_r": "t13d1517h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,ca34,fe0d,ff01_0904,0905,0906,0403,0804,0401,0503,0805,0501,0806,0601",
      "peetprint_hash": "fc97c1cdfb1409c9a9326c1b726d1dee",
      "akamai_h2": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
      "akamai_h2_hash": "52d84b11737d980aef856699f885ca86",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/157.0.0.0 Safari/537.36"
    },
    "chrome_headless": {
      "version": "157.0.8103.38",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1517h2_8daaf6152771_cb7bf5808d99",
      "ja4_r": "t13d1517h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,ca34,fe0d,ff01_0904,0905,0906,0403,0804,0401,0503,0805,0501,0806,0601",
      "peetprint_hash": "fc97c1cdfb1409c9a9326c1b726d1dee",
      "akamai_h2": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
      "akamai_h2_hash": "52d84b11737d980aef856699f885ca86",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/157.0.0.0 Safari/537.36"
    },
    "edge": {
      "version": "156.0.4314.58",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1516h2_8daaf6152771_806a8c22fdea",
      "ja4_r": "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0904,0905,0906,0403,0804,0401,0503,0805,0501,0806,0601",
      "peetprint_hash": "8f568b2a409b82d0ec24cb84bf3bc3fc",
      "akamai_h2": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
      "akamai_h2_hash": "52d84b11737d980aef856699f885ca86",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36 Edg/156.0.0.0"
    },
    "brave": {
      "version": "156.0.8078.80",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1516h2_8daaf6152771_806a8c22fdea",
      "ja4_r": "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0904,0905,0906,0403,0804,0401,0503,0805,0501,0806,0601",
      "peetprint_hash": "8f568b2a409b82d0ec24cb84bf3bc3fc",
      "akamai_h2": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
      "akamai_h2_hash": "52d84b11737d980aef856699f885ca86",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36"
    },
    "opera": {
      "version": "155.0.8059.85",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1517h2_8daaf6152771_cb7bf5808d99",
      "ja4_r": "t13d1517h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,ca34,fe0d,ff01_0904,0905,0906,0403,0804,0401,0503,0805,0501,0806,0601",
      "peetprint_hash": "fc97c1cdfb1409c9a9326c1b726d1dee",
      "akamai_h2": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
      "akamai_h2_hash": "52d84b11737d980aef856699f885ca86",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/155.0.0.0 Safari/537.36 OPR/139.0.0.0"
    },
    "yandex": {
      "version": "203.0.113.1",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1517h2_8daaf6152771_cb7bf5808d99",
      "ja4_r": "t13d1517h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,ca34,fe0d,ff01_0904,0905,0906,0403,0804,0401,0503,0805,0501,0806,0601",
      "peetprint_hash": "fc97c1cdfb1409c9a9326c1b726d1dee",
      "akamai_h2": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
      "akamai_h2_hash": "52d84b11737d980aef856699f885ca86",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/152.0.0.0 YaBrowser/26.10.0.0 Safari/537.36"
    },
    "whale": {
      "version": "150.0.7871.252",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1516h2_8daaf6152771_806a8c22fdea",
      "ja4_r": "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0904,0905,0906,0403,0804,0401,0503,0805,0501,0806,0601",
      "peetprint_hash": "67c3e9111bed9e7f03d2f21d6d88994b",
      "akamai_h2": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
      "akamai_h2_hash": "52d84b11737d980aef856699f885ca86",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/150.0.0.0 Whale/5.39.412.57 Safari/537.36"
    },
    "chromium_headless": {
      "version": "148.0.7778.96",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1516h2_8daaf6152771_d8a2da3f94cd",
      "ja4_r": "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
      "peetprint_hash": "1d4ffe9b0e34acac0bd883fa7f79d7b5",
      "akamai_h2": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
      "akamai_h2_hash": "52d84b11737d980aef856699f885ca86",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/148.0.7778.96 Safari/537.36"
    },
    "patchright": {
      "version": "148.0.7778.96",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1516h2_8daaf6152771_d8a2da3f94cd",
      "ja4_r": "t13d1516h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,0023,002b,002d,0033,44cd,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601",
      "peetprint_hash": "1d4ffe9b0e34acac0bd883fa7f79d7b5",
      "akamai_h2": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
      "akamai_h2_hash": "52d84b11737d980aef856699f885ca86",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
    },
    "firefox": {
      "version": "150.0.2",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1617h2_86a278354501_3cbfd9057e0d",
      "ja4_r": "t13d1617h2_002f,0035,009c,009d,1301,1302,1303,c00a,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,001c,0022,0023,002b,002d,0033,fe0d,ff01_0403,0503,0603,0804,0805,0806,0401,0501,0601,0203,0201",
      "peetprint_hash": "fd4547eeb41f073156b7bc8125a79a3c",
      "akamai_h2": "1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s",
      "akamai_h2_hash": "6ea73faa8fc5aac76bded7bd238f6433",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:150.0) Gecko/20100101 Firefox/150.0"
    },
    "webkit": {
      "version": "26.4",
      "source": "tls.peet.ws",
      "http_version": "HTTP/1.1",
      "ja4": "t13d2913h1_723694b0fccc_5671b5df5029",
      "ja4_r": "t13d2913h1_002f,0033,0035,0039,009c,009d,009e,009f,1301,1302,1303,1304,c009,c00a,c013,c014,c02b,c02c,c02f,c030,c09c,c09d,c09e,c09f,c0ac,c0ad,cca8,cca9,ccaa_0005,000a,000b,000d,0015,001c,0023,002b,002d,0033,ff01_0401,0809,0804,0403,0807,0501,080a,0805,0503,0808,0601,080b,0806,0603,0201,0203",
      "peetprint_hash": "e2a4f6ec1c75767498bc87d934bfccec",
      "akamai_h2": null,
      "akamai_h2_hash": null,
      "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.4 Safari/605.1.15"
    },
    "camoufox": {
      "version": "156.0.1-beta.34",
      "source": "tls.peet.ws",
      "http_version": "h2",
      "ja4": "t13d1517h2_8daaf6152771_3cbfd9057e0d",
      "ja4_r": "t13d1517h2_002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9_0005,000a,000b,000d,0012,0017,001b,001c,0022,0023,002b,002d,0033,fe0d,ff01_0403,0503,0603,0804,0805,0806,0401,0501,0601,0203,0201",
      "peetprint_hash": "a4bbbc7e3adf3db97d276fd0adb1efc4",
      "akamai_h2": "1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s",
      "akamai_h2_hash": "6ea73faa8fc5aac76bded7bd238f6433",
      "user_agent": "Mozilla/5.0 (X11; Linux x86_64; rv:156.0) Gecko/20100101 Firefox/156.0"
    }
  }
}

Takeaways