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. TLS is the encryption layer behind https, and the Client Hello is the first message a client sends when opening a connection - it lists the encryption settings the client supports. Created at Salesforce, JA3 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 that string into a 32-character fingerprint (MD5 turns any input into a fixed-length code). Because the hash comes 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) no matter 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, always in this order:

TLSVersion,Ciphers,Extensions,EllipticCurves,PointFormats

A real example looks like 771,4865-4866-4867-49195-...,0-23-65281-10-11-...,29-23-24,0, and the MD5 of that string is the JA3 fingerprint. The key idea 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 (the encryption library Chrome ships with). Python's requests (which uses OpenSSL), Go's crypto/tls, and Node each produce a different, recognizable Client Hello. So a scraper that sends a flawless Chrome User-Agent over Python's TLS stack ends up with a JA3 that screams "Python" - a contradiction the server spots before reading a single header.

JA3's weakness and why JA4 replaced it

JA3 has a real flaw: it depends on the order of the fields, and modern Chrome deliberately shuffles the order of its TLS extensions on every connection (part of GREASE, a scheme that keeps servers from hard-coding assumptions about clients). Because of that shuffling, real Chrome produces a different JA3 hash each time it connects, so you cannot match it against one 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 reshuffling no longer changes the result), splits the hash into readable segments, and adds a transport/version prefix. Most vendors now compute JA4 (and the wider JA4+ suite, including JA4H for HTTP/2), but JA3 is still widely deployed and remains the term most engineers recognize, so the two coexist.

Why a JA3 match depends on the TLS stack

A JA3 value is determined below the HTTP layer, so it reflects the underlying TLS client rather than anything set in the application. A client's JA3 matches a given browser only when it sends the same Client Hello that browser's TLS library produces. In practice this is why certain tools share a browser's JA3:

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

Coherence is the broader point: a JA3 that matches a browser is only one signal. Vendors cross-check the TLS fingerprint against the HTTP/2 preface and the Client Hints, so a request whose TLS layer looks like Chrome but whose HTTP/2 layer looks like a library is internally inconsistent. The signals only agree end-to-end on a genuine browser stack.

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 identifies the stack.
A client shares Chrome's JA3 only when its TLS library emits Chrome's Client Hello
(curl_cffi / tls-client / a real browser); HTTP/2 + headers must also be coherent.

Related terms

What Is TLS Fingerprinting (JA3/JA4)?
TLS fingerprinting is a way to recognize what software made a connection just by looking at how it sets up encryption — before the server re…
What Is JA4 Fingerprinting?
JA4 is a way to identify a browser by the fingerprint of its TLS handshake — TLS being the encryption layer behind https. It replaced the ol…
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. Think of it…
What Is curl_cffi?
curl_cffi is a Python HTTP client whose TLS fingerprint looks exactly like real Chrome, Firefox, or Safari. TLS is the encryption layer behi…
Anti-Bot Vendor Detection Cheatsheet
A useful first step when working with any protected site you are authorized to access is identifying which anti-bot vendor sits in front of …
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 tell automated traffic apart from real human visitors — and then block, challeng…
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…
How Browser Fingerprinting Works
Browser fingerprinting is how a site combines signals — canvas, WebGL, audio, fonts, navigator probes, TLS (the encryption layer behind http…
How Do Websites Detect Web Scrapers?
Websites spot scrapers by gathering hundreds of small clues about each visitor, then scoring how human the whole picture looks. No single cl…

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 the order they appear on the wire, which makes it unstable against Chrome's extension-order shuffling (GREASE). JA4 sorts the cipher and extension lists before hashing and splits the result into readable segments, so it stays stable per client and tells you more. 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 lives at the application layer, while JA3 is computed from the TLS handshake underneath it. Python's OpenSSL stack produces a different Client Hello than Chrome's BoringSSL, so the JA3 reveals Python no matter what headers you set. 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-intelligence 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-31