For most of the 2010s, the standard advice for "looking like a real browser" topped out at sending the rightUser-Agent. By 2018, JA3 — a fingerprint over the TLS ClientHello — quietly turned that advice into a museum piece. By 2024, JA4+ closed every loophole JA3 had.
The problem with JA3
JA3 hashes the cipher suites, extensions, and elliptic curves a client offers in its TLS ClientHello. Two clients with identical TCP-layer behavior produce the same hash. Cloudflare, Akamai, and every other major WAF use this as a stable identifier — they can tell the difference between Chrome 131 and a Python requestsscript before they've parsed a single byte of HTTP.
What changed in 2024–2026
TLS 1.3 introduced extension permutation (RFC 8701) — extensions are sent in a randomized order. JA3, which hashes the extension list directly, started producing unstable fingerprints. JA4+ fixes this by sorting the extension list before hashing. It also adds HTTP/2 frame ordering and ALPN sequence as separate sub-fingerprints.
How curl-impersonate keeps up
curl-impersonate patches curl to send the exact ClientHello of a real Chrome / Firefox / Safari build, including extension order, cipher suite preferences, and HTTP/2 settings frames. curl_cffi wraps it in Python.
from curl_cffi.requests import AsyncSession
async with AsyncSession(impersonate="chrome131") as s:
resp = await s.get("https://target.example.com/")Why it isn't enough alone
A perfect TLS fingerprint paired with a Python User-Agent is an instant flag. The whole stack — TLS, HTTP/2, User-Agent, Accept-Language, header order — has to come from the same browser version. That's why Scrape pins impersonation per session and rotates only between sessions, not within them.