Explained
When your client opens a TLS connection, the very first bytes are a ClientHello packet describing what cipher suites, TLS versions, extensions, elliptic curves, and ALPN values your client supports. The exact list and ordering of these attributes is implementation-specific — Chrome's ClientHello looks different from Firefox's, which looks different from curl's, which looks different from Python's `requests` library, and so on.
JA3 (and its successor JA4) is a hash format that turns the structure of the ClientHello into a short identifier. Anti-bot vendors compute this hash for every incoming TLS connection and check it against known signatures. If your scraper uses Python `requests`, your TLS fingerprint matches the OpenSSL default and is instantly identifiable as 'not a real browser' — even before you send a single HTTP byte.
This is why so many scrapers fail on Cloudflare, Akamai, and similar stacks even when the IP and User-Agent look correct. The TLS layer gives away that the request didn't come from Chrome. Modern stealth libraries (like `curl_cffi`, `tls-client`, Playwright with the right launch flags) impersonate real browser TLS fingerprints to avoid this.
How It Works
JA3 builds the fingerprint from five fields of the ClientHello: TLS version, supported cipher suites, supported extensions, supported elliptic curves, and supported elliptic curve point formats. It joins them, MD5-hashes the result, and produces a 32-character signature.
JA4 (the modern replacement) extends this with ALPN, version, SNI presence, GREASE handling, and orders extensions in a stable way that's resilient to randomization. JA4 also has variants for QUIC (JA4Q), HTTP (JA4H), and SSL session (JA4S). Servers compute the fingerprint and either look it up against allow/deny lists or feed it into a risk-scoring model alongside other signals.