Anti-Bot

What Is JA3 Fingerprinting?

What Is JA3 Fingerprinting? — conceptual illustration
On this page

JA3 is a method for fingerprinting a TLS client by hashing the fields of its Client Hello. Created at Salesforce, it concatenates the TLS version, the ordered list of cipher suites, the ordered list of extensions, the elliptic curves, and the curve point formats, then MD5-hashes the string into a 32-character fingerprint. Because the hash is computed from the very first packet of the handshake - before any HTTP request, User-Agent, or cookie - JA3 lets a server identify the client library (real Chrome vs Python vs Go) regardless of what the application layer claims.

Quick facts

HashesTLS version + ciphers + extensions + curves + point formats (MD5)
Output32-char hex string, computed from the Client Hello
CatchesPython/requests, Go, Node, curl - their TLS stacks differ from browsers
WeaknessExtension order randomization (Chrome GREASE) makes raw JA3 unstable
SuccessorJA4 - sorts fields and adds structure to fix JA3 instability

What goes into the hash

JA3 builds a comma-and-dash-delimited string from five Client Hello fields, in order:

TLSVersion,Ciphers,Extensions,EllipticCurves,PointFormats

For example 771,4865-4866-4867-49195-...,0-23-65281-10-11-...,29-23-24,0, then MD5 of that string is the JA3. The decisive insight is that this captures the TLS library, not the application. Real Chrome offers a specific set of ciphers and extensions in a specific order baked into BoringSSL. Python's requests (OpenSSL), Go's crypto/tls, and Node each produce a different, recognizable Client Hello. So a scraper sending a flawless Chrome User-Agent over Python's TLS stack has a JA3 that screams "Python" - a contradiction the server sees before reading a single header.

JA3's weakness and why JA4 replaced it

JA3 has a real flaw: it is sensitive to field order, and modern Chrome deliberately randomizes the order of TLS extensions on every connection (part of the GREASE anti-ossification scheme). That means real Chrome produces a different JA3 hash per connection, so you cannot match against a single known-Chrome JA3 - the raw hash is unstable for exactly the clients you most want to allow.

The fix is JA4, which sorts the cipher and extension lists before hashing (so order randomization no longer changes the result), separates the hash into readable segments, and adds a transport/version prefix. Most vendors now compute JA4 (and the JA4+ suite including JA4H for HTTP/2), but JA3 remains widely deployed and is still the term most engineers recognize, so the two coexist.

Defeating JA3 means impersonating the TLS stack

You cannot fix a JA3 mismatch at the HTTP layer - it is computed below it. The only fix is to make your client emit a Client Hello byte-identical to a real browser. Options:

  • curl-impersonate / curl_cffi - curl rebuilt against BoringSSL with Chrome's exact cipher/extension configuration.
  • TLS-impersonating libraries - tls-client (Go), rnet/noble-tls, which ship per-browser Client Hello profiles.
  • A real browser - Playwright/Puppeteer driving real Chrome naturally has Chrome's JA3/JA4.

The catch is coherence: matching JA3 is not enough if your HTTP/2 fingerprint or headers still look like a library. Vendors cross-check the TLS fingerprint against the HTTP/2 preface and the Client Hints - a request that is Chrome on JA3 but Go on HTTP/2 is still caught. Impersonation has to be end-to-end.

Code example

text
JA3 string  =  TLSVersion,Ciphers,Extensions,Curves,PointFormats
            =  771,4865-4866-4867-49195-49199-...,0-23-65281-10-11-35-16-5-13-...,29-23-24-25,0
JA3 hash    =  MD5(JA3 string)  ->  e.g. cd08e31494f9531f560d64c695473da9

Same site, three clients hitting the same URL:
  real Chrome 131   JA3 ~ 'cd08e31494...'  (order randomized by GREASE)
  python requests   JA3   'a0e9f5d64b...'  (OpenSSL cipher set + order)
  go net/http       JA3   '1f3a8d...'      (crypto/tls cipher set)

The Chrome User-Agent does not matter - the Client Hello gives the stack away.
Fix: emit Chrome's exact Client Hello (curl_cffi / tls-client / a real browser),
and make sure HTTP/2 + headers match too, or the coherence check still fails.

Related terms

What Is TLS Fingerprinting (JA3/JA4)?
TLS fingerprinting is a technique that identifies an HTTP client from its TLS handshake — before the server reads a single request byte. The…
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…
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/…
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…
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 Browser Fingerprinting?
Browser fingerprinting is a technique that identifies and tracks a visitor by combining dozens of small, observable characteristics of their…
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 Headless Browser Detection?
Headless browser detection is the set of probes anti-bot systems use to distinguish a headless or instrumented Chrome session from a real us…
What Is Browser Fingerprinting Evasion?
Browser fingerprinting evasion is the practice of configuring an automated browser so that the combined fingerprint it presents — canvas, We…
How Do Websites Detect Web Scrapers?
Websites detect scrapers by collecting hundreds of signals across the network, transport, browser, and behavioral layers, then scoring the c…

Concept map

How JA3 Fingerprinting 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 MD5-hashes the TLS Client Hello fields in their wire order, which makes it unstable against Chrome's extension-order randomization (GREASE). JA4 sorts the cipher and extension lists before hashing and splits the result into readable segments, so it is stable per client and more informative. JA4 is the modern successor, but JA3 is still widely deployed.

Why does my Python scraper get blocked even with a perfect Chrome User-Agent?

Because the User-Agent is application-layer and JA3 is computed from the TLS handshake below it. Python's OpenSSL stack produces a different Client Hello than Chrome's BoringSSL, so the JA3 reveals Python regardless of headers. You need a TLS-impersonating client like curl_cffi or tls-client, and the HTTP/2 fingerprint has to match too.

Is JA3 still used if JA4 exists?

Yes. Many anti-bot systems and detection tools compute both, and a lot of existing rules and threat-intel are written in terms of JA3. JA4 is the better signal, but JA3 has not gone away - treat them as complementary TLS fingerprints.

Last updated: 2026-05-30