TLS fingerprinting identifies a client from the shape of its TLS handshake — which cipher suites, extensions, and elliptic curves it offers, and in what order — independent of any HTTP header. JA3 and its successor JA4 are the standard formats for hashing that shape into a short, comparable string.
Before a single HTTP header is sent, a TLS client announces itself in the ClientHello message: TLS version, cipher suite list, extensions, and elliptic curve support, each in the order the underlying library constructs them. JA3 (introduced by Salesforce's security team in 2017) hashes those fields into an MD5 string; JA4 (2023, FoxIO) extends the approach with a more structured, less collision-prone format. Two clients with identical User-Agent headers can produce completely different JA3/JA4 hashes if they're built on different TLS stacks.
That's the point of the technique: it reads something a client can't easily fake through headers, because the handshake shape comes from the networking library itself — Python's ssl module, Go's crypto/tls, or a real browser's engine — not from anything the script author writes.
A scraper can set a perfect Chrome User-Agent and matching headers and still get flagged, because Python's requests library, curl, and Go's HTTP client all produce a TLS handshake shape that doesn't match real Chrome's — anti-bot systems maintain databases mapping known JA3/JA4 hashes to the client that actually produced them, and a Chrome-labeled request with a python-requests JA3 hash is an immediate, high-confidence signal of spoofing.
Closing that gap requires a networking stack that reproduces a real browser's TLS handshake, not just its headers — libraries like curl-impersonate or curl_cffi exist specifically to replicate a browser's exact handshake shape, since patching headers alone leaves the fingerprint underneath unchanged.
from curl_cffi import requests
# Reproduces Chrome's actual TLS handshake shape, not just its headers.
response = requests.get("https://example.com", impersonate="chrome124", timeout=15)How Crawlora handles this
Crawlora's requests present a matched TLS fingerprint alongside the headers and browser fingerprint — the JA3/JA4 hash lines up with the browser identity being claimed — so the handshake doesn't contradict everything else about the request the way a stock HTTP client's does.
Glossary
FAQ
Yes — several public JA3/JA4 test endpoints echo back the hash your client produced on connection, which is a quick way to confirm whether your HTTP stack's handshake actually matches the browser identity your headers claim.
It's one layer of it. Browser fingerprinting is the broader technique combining network signals (including TLS), JavaScript-visible browser properties, and behavior. TLS fingerprinting specifically covers the handshake, which is readable before any page content or JavaScript runs at all.
No — the User-Agent is an HTTP header the client chooses to send; the TLS fingerprint comes from the networking library's handshake and doesn't change just because a header value changed. Fixing the mismatch requires a stack that reproduces the claimed browser's actual handshake.
Beyond TLS Fingerprinting (JA3/JA4), Crawlora's own docs cover the rest of the stack — browse the APIs, test a request in Playground, and move from scraping infrastructure work to production data workflows.