TLS Fingerprinting and JA3 Signatures in Bot Detection
JA4 replaces the fragile JA3 method for identifying bots via encrypted connections.

The TLS handshake gives away more about a client than most engineers realize. Before a single byte of a web page loads, before any JavaScript runs or cookie gets set, the Client Hello packet exposes which cryptographic library built it, and that fact alone lets defenders separate real browsers from automated traffic before application logic ever gets a chance to run. That packet is where JA3 and, now, JA4 got their start.
HTTPS runs on top of TLS, and TLS starts every session with a negotiation over which encryption to use. That negotiation, the handshake, happens before encryption kicks in, so the Client Hello message travels in plaintext across the wire. Anyone watching the connection, not just the two endpoints, can read what the client proposed: which TLS versions it's willing to speak, which cipher suites it supports and in what order, which extensions it lists, which elliptic curves and point formats it offers, and which application protocol it prefers over ALPN (HTTP/2 versus HTTP/3, mainly).
None of that is decided by the web page or the app calling it. It's decided by the TLS library sitting underneath: BoringSSL in Chrome, NSS in Firefox, OpenSSL on most Linux servers and countless scripts, SChannel on Windows, Go's crypto/tls package, Node's tls module. Each one builds its Client Hello slightly differently, in ways that are stable and repeatable for that library and version. That's the entire premise TLS fingerprinting rests on: the library's fingerprint.
TLS fingerprinting behaves very differently from browser fingerprinting. Swapping browsers, spoofing a user agent, or flipping on a privacy mode changes what JavaScript reports about the environment, but none of it touches the TLS layer, since that packet gets built and sent before any user code runs. Changing a TLS fingerprint means changing the TLS library itself. That's a much higher bar to clear, and that's why the technique has held up as a detection layer for the better part of a decade.
JA3 and the Client Hello as a 32-character identity token
Salesforce built JA3 in 2017 and open-sourced it the same year, and the method behind it is almost mechanical in its simplicity. Pull five fields out of the Client Hello: SSL/TLS version, cipher suite list, extension list, elliptic curves, and EC point formats. Turn each list into dash-separated decimal numbers, string the five together with commas, and run the result through MD5.
That produces a raw string that might look like 771,4865-4866-4867,0-23-65281,29-23-24,0, and hashing it produces a fixed 32-character hex string, the JA3 fingerprint. Every TLS client that constructs the same values in the same order lands on the same hash, every time.
That property is what made JA3 valuable. A hash is easy to share. A security team at one company could find the JA3 hash tied to a piece of malware or a bot framework and hand it off to another team, another vendor, another threat intel feed, and everyone downstream could block that exact stack without needing to inspect a single packet themselves. JA3 gave the industry a common shorthand for "this is the same client software," and for a few years, that shorthand held.
JA3's breakdown: GREASE, extension randomization, and the fingerprint explosion
The first crack came from an idea meant to help the TLS ecosystem, not hurt fingerprinting. Google introduced GREASE (RFC 8701) to stop the internet from ossifying around a fixed set of TLS values. If every server and middlebox only ever sees a small set of known cipher suites and extensions, some of them start hard-coding assumptions around that set, and the protocol loses its ability to evolve. GREASE fixes that by having clients throw in reserved, meaningless values, deliberately, scattered into cipher suites and extensions, so nothing downstream can assume the list is fixed.
That's good for protocol health and bad for naive JA3 implementations. Unless the implementation explicitly strips GREASE values before hashing, the same browser produces a different JA3 hash on every single connection, since the GREASE values shift each time. A fingerprint that changes every session isn't a fingerprint anymore.
Chrome made the problem worse starting with Chrome 108, when it began randomizing the order of TLS extensions in the Client Hello. The same Chrome version, on the same machine, now produces different JA3 hashes across sessions with no meaningful relationship between them. That single change broke browser identification by JA3 across a huge share of real-world traffic, since Chrome accounts for most browser traffic on the open web.
Even outside of deliberate randomization, JA3 was fragile by construction. Append one new cipher suite in a routine browser update and the entire hash shifts, no matter how minor the change actually was to the client's real behavior. Any library that reorders its lists or updates its supported cipher set breaks its own fingerprint history. Defenders chasing JA3 hashes were chasing a moving target that moved every time a vendor shipped a patch.
The scale of the fallout is well documented. Tracking of distinct TLS fingerprints seen globally showed the number jump from 18,652 in August 2018 to more than 1.3 billion by the end of February 2019. That's not organic diversity in TLS clients; it's the security community's interest in JA3 (and its server-side counterpart, JA3S) reaching malware authors and bot operators, who rapidly built circumvention into their tooling once they understood what was being fingerprinted and why.
JA4's fixes for the structural flaws JA3 could not overcome
John Althouse, one of the original authors behind JA3, built JA4 at FoxIO and released it in 2023 as the centerpiece of a broader suite called JA4+. The fix for JA3's core weakness turns out to be almost embarrassingly simple in hindsight: sort the values before hashing them.
Cipher suites and extensions get sorted into a canonical order before JA4 runs SHA-256 over them. Randomize the order all you want, GREASE values and all, and the sorted list comes out identical, so the hash comes out identical too. Extension randomization, cipher list shuffling, and GREASE insertion, the entire class of evasion that broke JA3, stops mattering, because JA4 never looks at order.
JA4 also abandons the opaque MD5 blob in favor of a readable, three-part structure written as a_b_c. Part A is a human-readable metadata prefix listing protocol type (TCP or QUIC), TLS version, whether SNI carries a domain or an IP, a two-digit cipher count, a two-digit extension count, and the first ALPN value. A string like t13d1516h2 decodes directly: TCP, TLS 1.3, domain-based SNI, 15 cipher suites, 16 extensions, HTTP/2. An analyst can read that and understand the client's shape without running it through a lookup table.
Part B is a truncated SHA-256 hash of the sorted cipher suite list, immune to reordering by construction. Part C does the same for the sorted extension list plus signature algorithms. Because the fingerprint is split into three independently meaningful segments rather than one monolithic hash, JA4 has a locality property JA3 never had: if one segment changes, the other two stay recognizable. A detection system can match on a partial fingerprint, flag an anomaly in just one segment, and still retain useful signal from the rest, rather than treating the whole fingerprint as either a full match or a total miss.
The broader JA4+ family: fingerprinting beyond the TLS layer
JA4 is one member of a larger family, not a standalone tool, and the JA4+ suite covers protocol layers well beyond the TLS Client Hello. JA4S fingerprints the server's response, the ServerHello, which turns out to be useful for spotting man-in-the-middle proxies sitting where they shouldn't be. JA4H fingerprints the HTTP client itself: header order, cookie presence, language preference, all sitting a layer above TLS in the application layer. JA4X handles X.509 certificates, JA4T covers raw TCP behavior, and JA4SSH covers SSH sessions.
JA4H deserves particular attention, because it exposes a blind spot in how most evasion attempts get built. A developer trying to slip past detection typically fixes the TLS layer and stops there, assuming the job is done once the Client Hello looks like a real browser's. But a large share of modern bot detection runs at the HTTP layer, not the TLS layer, reading header order and cookie behavior that JA4H captures directly. Fixing JA4 while leaving JA4H untouched still leaves a fully exposed, fully fingerprintable layer sitting right above it.
By 2026, JA4 has been adopted across the industry: Cloudflare, AWS WAF, VirusTotal, and Akamai all use it, and JA4+ functions as the de facto standard for TLS fingerprinting at this point, the way JA3 did in its earlier years. The standard keeps absorbing new signal, too. Chrome 141 and later ships a trust_anchors extension, and Chrome 151 and later adds post-quantum ML-DSA signature schemes, both of which feed straight into the JA4 hash when present, adding fresh discriminating detail as browsers themselves evolve.
Where TLS fingerprinting fits in the layered detection stack
TLS fingerprinting is one layer in a sequence. A connection gets evaluated at several points as it comes in: the TCP/IP fingerprint arrives before encryption even starts, the TLS Client Hello arrives during the handshake, HTTP/2 SETTINGS frames arrive as the first application-layer signal right after TLS completes, then HTTP header order and values, then Client Hints coherence, then IP reputation and ASN classification, then behavioral signals like mouse movement and interaction timing.
The advantage of catching problems this early in the chain is speed and cost. If the Client Hello looks wrong, the connection can be dropped or rerouted to a honeypot before HTTP even starts: the page never loads, no JavaScript ever runs, and no CAPTCHA ever needs to render. That's a cheaper, faster rejection than anything that depends on behavioral analysis further down the stack.
Research backs up how strong a signal the TLS fingerprint alone can be. A study (arXiv 2602.09606) trained two gradient-boosted classifiers, XGBoost and CatBoost, on a real JA4 fingerprint dataset called JA4DB. CatBoost hit an AUC of 0.998, an F1 score of 0.9734, and 98.63% accuracy. Feature importance analysis in that same study pointed to JA4's Part B (the cipher hash), cipher count, and extension count as the most influential features driving those numbers, matching what JA4's design set out to capture.
In practice, no vendor treats the fingerprint as a standalone verdict. Fraud and bot detection systems combine JA3 or JA4 with cookies, IP reputation, and behavioral features together to catch credential stuffing, carding, and scraping. The fingerprint narrows down what's connecting; the rest of the stack decides what to do about it.
What TLS fingerprinting catches and misses in real-world detection
Lab accuracy and field accuracy are two different numbers, and the gap between them matters. For sites running without a behavioral detection layer on top, TLS fingerprinting alone catches somewhere between 40 and 70 percent of automated traffic, with the exact figure depending heavily on how aggressively that particular site gets targeted for scraping.
That range sits well below the 98.63% accuracy the CatBoost study reported, and the gap isn't a contradiction, it's a constraint built into the classifier's own assumptions. The lab result assumes it's working with a genuine, unmodified fingerprint to classify. Sophisticated bot operators build their entire evasion strategy around attacking that exact assumption, feeding the classifier a fingerprint engineered to look legitimate.
A honeypot study found DataDome catching 55.44% of bot traffic and BotD catching 47.07%; a substantial share of automated traffic got through both systems. Those figures reflect detection capability at the time of the study, and the landscape has continued to shift as new techniques have been deployed on both sides.
The scale of the underlying problem is large by any measure. Bad bots make up 37% of all internet traffic. DataDome logged nearly 1.7 billion requests from OpenAI's crawlers in August 2025 alone, and 88.9% of robots.txt files explicitly disallow GPTBot, yet those directives get ignored routinely. Fingerprinting exists precisely because asking politely, via robots.txt, doesn't work at this scale.
Where bot evasion of TLS fingerprinting falls short
Extension order randomization used to be a reliable way to dodge JA3, since shuffling the extension list changed the hash on every connection. It does nothing against JA4, since JA4 sorts extensions before hashing them, and that particular loophole is closed for good. Randomly shuffling the cipher suite list worked the same way against JA3 and fails the same way against JA4's sorted Part B.
JA3 impersonation, hardcoding a known browser's exact cipher and extension lists into a script, still works against JA3 detection on its own. Against JA4, it gets harder to pull off cleanly, because the fingerprint's multi-part structure means Part A's metadata (cipher count, extension count, ALPN value) has to line up with Parts B and C or the mismatch gives the impersonation away, even when the hashed portions look correct.
Library-level tools handle this problem differently and with more success. Python's requests and urllib3 libraries produce a Client Hello that looks nothing like Chrome's, full stop, no matter how the request is dressed up at the application layer. Tools like curl-impersonate and curl_cffi solve that by bundling a genuine browser TLS stack behind a requests-like API, offering browser impersonation, and, in curl-impersonate's case, Edge. That's a small code change that fixes the entire TLS layer at once, since the underlying stack producing the Client Hello is a real browser's stack rather than an approximation of one.
VPNs and standard SOCKS proxies don't touch the TLS fingerprint. They change the network path traffic takes, not the Client Hello itself, so the fingerprint stays what it was before the proxy got involved. An intercepting HTTPS proxy, mitmproxy or a corporate inspection proxy, does change the fingerprint, since it terminates the original TLS session and negotiates a new one, but that usually makes detection easier rather than harder, because the proxy's own TLS configuration rarely matches what a real browser would send.
Anti-censorship tools like Xray with the REALITY protocol, or AmneziaWG, take a more deliberate approach, attempting to reproduce a popular browser's exact Client Hello field ordering to blend in with ordinary traffic. Done with real precision, this works. In practice, most implementations leave small artifacts behind, in extension ordering or in how the SNI field gets constructed, and active probing can still pick those artifacts out even when the fingerprint looks convincing at a glance.

