Anti-Bot

What Is TLS Fingerprinting (JA3/JA4)?

What Is TLS Fingerprinting (JA3/JA4)? — conceptual illustration
On this page

TLS fingerprinting is a technique that identifies an HTTP client from its TLS handshake — before the server reads a single request byte. The handshake's cipher list, extensions, elliptic curves, and ALPN values are hashed into a stable identifier (JA3 in 2017, JA4+ since 2023). Anti-bot vendors at the CDN edge match those hashes against a catalogue of every popular HTTP library, so a scraper using Python requests can be flagged before its headers even arrive.

Quick facts

StandardJA4+ (2023, FoxIO) — replaces JA3 (2017, Salesforce)
Computed atCDN edge (Cloudflare Rust crate, Akamai EdgeWorker)
Fields hashedTLS version, cipher suites, extensions, curves, ALPN
Defeatsrequests, httpx, urllib3, curl (default), Go net/http
Bypassed bycurl_cffi, tls-client, real browsers, Camoufox

How TLS fingerprinting works

A TLS Client Hello carries a fixed-position list of fields — protocol version, supported cipher suites, named curves, point formats, signature algorithms, and the order of TLS extensions. Each combination is distinctive to the library that built the handshake. Python requests looks nothing like Chrome; Go net/http looks nothing like Firefox; curl looks like none of them. Anti-bot vendors hash these fields and compare the result to known browser baselines.

The hash is computed at the CDN edge, before any HTTP byte is parsed, before any HTML is served, before any JavaScript runs. This is why a perfect User-Agent spoof from requests still gets blocked — the TLS layer already failed.

JA3 vs JA4 — what changed in 2023

The first widely adopted fingerprint was JA3 (Salesforce, 2017): an MD5 hash of SSLVersion,Cipher,Extensions,EllipticCurve,EllipticCurvePointFormat. It worked for years but had two problems — TLS 1.3 GREASE values (random padding) made the hash unstable, and a single MD5 hid which field actually changed.

JA4+ (FoxIO, 2023) is the replacement. Instead of one MD5 it produces a structured fingerprint with separate components for the TLS Client Hello (JA4), the HTTP/2 client preface and SETTINGS frame (JA4H), the TLS server response (JA4S), and TCP-level timings (JA4T). Each component is independently inspectable, GREASE-stable, and human-readable.

FingerprintWhat it coversStatus
JA3TLS Client Hello (MD5)Legacy — still seen on older WAFs
JA4TLS Client Hello, GREASE-stableCurrent standard at major vendors
JA4HHTTP/2 client preface + SETTINGS frame orderRequired to pass Akamai, Cloudflare
JA4SServer-side TLS response fingerprintUsed by vendors to detect MITM proxies
JA4TTCP options + window size + timingRare — DataDome experimental

If you are still targeting JA3-only on a 2026 site, your fingerprint will look correct in tls.peet.ws but block in production. Match JA4 + JA4H together.

HTTP/2 framing — the layer underneath TLS

Even if the TLS handshake matches Chrome exactly, the HTTP/2 connection that follows has its own fingerprint. Chrome sends a specific SETTINGS frame (HEADER_TABLE_SIZE=65536, INITIAL_WINDOW_SIZE=6291456, MAX_HEADER_LIST_SIZE=262144) followed by a WINDOW_UPDATE frame with a delta of 15663105. The frame order and exact values change per Chrome major version.

Go's net/http2 and Python's httpx use different SETTINGS values and different frame ordering. Vendors hash the SETTINGS frame body and compare. curl_cffi ships with the Chrome-matching values baked in; tls-client and noble-tls require manual configuration. This is the second-most-common reason a JA4-correct request still gets a 403.

QUIC and HTTP/3 — the next surface

Cloudflare and Google now serve a significant share of traffic over QUIC (HTTP/3 over UDP). QUIC has its own handshake, its own SETTINGS frame equivalent, and its own fingerprint surface — and almost no scraping library implements it yet. Most scrapers fall back to HTTP/2 by sending an Alt-Svc-incompatible header set, which is itself a signal.

For now QUIC-aware fingerprinting is rare in production blocks but expect this surface to matter by late 2026, particularly on Google properties and Cloudflare's premium tier. The mitigation is the same: pick a library that handshakes the way Chrome does end-to-end.

Why this matters for scraping

If your scraper sends a User-Agent header claiming to be Chrome 131 on macOS, but its JA4 hash matches Python urllib3, you flag faster than if you had not spoofed anything — because the mismatch itself is the signal. Header spoofing without TLS spoofing is the single most common rookie mistake in modern scraping.

JA4+ also covers JA4H (HTTP header order and content), JA4X (X.509 certs), and JA4T (TCP options + window). The picture an anti-bot builds extends well beyond the cipher list.

How scrapers defeat it

The fix is a TLS-impersonating HTTP client. curl_cffi wraps curl-impersonate, a patched build of curl that uses BoringSSL (Chrome's TLS library) with Chrome's exact cipher list and HTTP/2 SETTINGS frames. A single impersonate="chrome131" argument gives you a JA4 indistinguishable from real Chrome. tls-client (Go), noble-tls, and hrequests do the same in their respective ecosystems. Real browsers — Chrome, Firefox, Camoufox — speak real TLS by definition.

Keep impersonation profiles fresh. A Chrome 120 fingerprint in 2026 is itself suspicious because real users updated.

Code example

python
from curl_cffi import requests

# One argument replaces the entire TLS stack with Chrome's
r = requests.get(
    "https://protected-site.com/api",
    impersonate="chrome131",
    proxies={"https": "http://user:pass@residential-proxy:port"},
    timeout=20,
)
print(r.status_code, len(r.text))

Related terms

What Is Anti-Bot Detection?
Anti-bot detection is the set of techniques websites use to distinguish automated traffic from human users — and to block, challenge, or thr…
What Is Browser Fingerprinting?
Browser fingerprinting is a technique that identifies and tracks a visitor by combining dozens of small, observable characteristics of their…
What Is curl_cffi?
curl_cffi is a Python HTTP client that produces TLS fingerprints identical to real Chrome, Firefox, or Safari. It wraps curl-impersonate — a…
What Is Cloudflare Turnstile?
Cloudflare Turnstile is a CAPTCHA-replacement service that verifies a visitor is a human without showing a traditional puzzle. It runs a ser…
What Is HTTP/2 Fingerprinting?
HTTP/2 fingerprinting identifies an HTTP client from its SETTINGS frame and frame-level behaviour, independent of the TLS layer. Every HTTP/…
Anti-Bot Vendor Detection Cheatsheet
The first step of any scrape against a protected site is identifying which anti-bot vendor is in front of it. The vendor determines almost e…
What Is the Scrapy + Go TLS Sidecar Architecture?
The Scrapy + Go TLS sidecar architecture is the most common production pattern for scraping Akamai- and Cloudflare-protected sites at scale.…
What Is JA4 Fingerprinting?
JA4 is a TLS client fingerprint that replaced JA3 after Chrome began randomising the order of its TLS extensions. JA3 hashed the extension l…
How Does Deobfuscation Work?
Deobfuscation is the process of turning deliberately unreadable code back into something a human can read and reason about. Obfuscators neve…

Concept map

How TLS Fingerprinting (JA3/JA4) connects

The terms most directly tied to this one. Hover a node to see its neighbours, click to preview, drag to rearrange.

0 terms · 0 connections
You are here · Anti-Bot
Building map…

Frequently asked questions

What is the difference between JA3 and JA4?

JA3 (2017) hashed TLS extensions in the order they were sent. Chrome started randomising that order in 2022, which made JA3 unstable — the same browser produced different hashes across sessions. JA4 (2023) sorts extensions alphabetically and strips GREASE values before hashing, so the fingerprint stays stable regardless of randomisation. JA4+ extends the idea to HTTP headers, certificates, and TCP options.

Can I just spoof my User-Agent to fix this?

No — and doing so usually makes detection faster, not slower. The User-Agent is read after the TLS handshake. If your User-Agent claims Chrome but the TLS hash matches Python urllib3, the mismatch is itself a strong bot signal. You need to spoof the TLS layer too.

Does using HTTPS hide the fingerprint?

No — the TLS ClientHello is sent in the clear at the start of every HTTPS connection. The handshake is the thing being fingerprinted, before any encrypted application data flows.

Which Python library should I use?

curl_cffi is the most popular drop-in replacement for requests, supports Chrome, Firefox, and Safari impersonation profiles, and handles HTTP/2 SETTINGS frames correctly. It is the default first-step upgrade for any Python scraper hitting a real anti-bot.

Is JA3 still useful, or do I have to target JA4 now?

For older WAFs (Imperva, AWS WAF Bot Control on default settings) JA3 alone is still enough to score the request. For Akamai, Cloudflare Bot Management, DataDome, and PerimeterX in 2026, JA4 and JA4H are scored together — matching only JA3 produces a "wrong-shape Chrome" signal that ranks bot.

Do I need to handshake over QUIC to bypass Cloudflare today?

Not yet. Cloudflare still accepts HTTP/2 from real Chrome on the vast majority of sites, and most scraping libraries don't support QUIC. But the gap between "scrapers do HTTP/2" and "real users do HTTP/3" is becoming a signal on Cloudflare's premium tier — worth tracking.

Last updated: 2026-05-27