Every bot says it is Chrome. The handshake says otherwise. How we read JA4 and HTTP/2 fingerprints at the edge, why we needed both, and where they fail.
Parts 4 and 5 of this series will be published over the coming days. Stay tuned.
Part 3 of 5 in the anti-bot series. New here? Start with Part 1, the overview: *Magento Anti-Bot and Anti-Scraping Guide.*
Every bot on the internet is Chrome (webkit, sort-of). Just ask it. Chrome on Windows, the latest version, with all the right headers, freshly copied from a real browser by someone who read a blog post much like this one.
The user agent is a name tag. Anyone can write anything on a name tag (a curl posted with a handwritten UA can bypass Cloudflare, maybe the free one, yet it did). What is much harder to fake is the way a client actually talks: how it negotiates encryption, and how it opens an HTTP/2 connection. Those habits live deep inside the network library, and most scraper authors never touch them.
This post is about reading those habits on every request at the edge of a Magento store: what JA4 and HTTP/2 fingerprints are, why we needed both, the afternoon I spent patching OpenResty from source, and the day fingerprints let us down.
Great if you haven't heard of it!
JA4: the handshake as handwriting
Before any page is requested, the client says hello to the server and lists what it supports: which versions of TLS, which ciphers, which extensions, which protocols it would like to speak next. Chrome says hello one way. Firefox another. Python's requests library, Go's HTTP client and curl each have their own accent.
JA4 is an open standard from FoxIO, launched in 2023, that turns that hello into a short fingerprint. It replaced the older JA3 for a very practical reason, as Cloudflare explains: in 2023 Chrome started shuffling the order of its extensions on every connection, which made JA3 fingerprints change constantly. JA4 sorts them first, so the shuffle stops mattering.
The trick is not identifying the browser. We do not care whether you are on Chrome or Firefox. We care whether the handshake belongs to a browser at all, when the name tag says it does.
HTTP/2: the layer they forget
Here is the problem with TLS fingerprints on their own: there are libraries whose whole job is to fake a browser's hello, byte for byte. Good ones. Scrapers use them.
But after the hello, if the connection speaks HTTP/2, the client sends its opening settings: window sizes, limits, and the order in which it lists certain headers. Every browser engine has its own habits here too, and they come from the HTTP stack, not the TLS library. Faking the handshake does not fake these. You have to fake the whole conversation, consistently.
Stock NGINX does not expose any of this. Neither does OpenResty. So we took the HTTP/2 part of an open-source fingerprinting patch (nginx-ssl-fingerprint), stripped out everything else, and compiled it into the NGINX core inside OpenResty. That meant giving up prebuilt images and building our own, from source, in stages.
The first version worked perfectly, in the sense that it compiled, started, and returned an empty fingerprint for every single request. A missing flag. Two days later it was reading real values.
OpenResty: the edge
Now the evidence. Across multiple production stores over a given period, 174,920 requests that claimed to be a browser had the connection of something else: 3.4% of all browser-claiming traffic, from 18,325 addresses.
The TLS fingerprint caught most of them: about 129,000 had handshake traits no current browser sends, and another 41,000 matched signatures of known scraping frameworks. The HTTP/2 fingerprint caught another 4,136 that the handshake missed: Python, curl and Java stacks, all wearing a Chrome name tag.
And the number that justifies building both: only 86 requests were caught by both fingerprints at once. They overlap almost nowhere. Each catches what the other misses. Remove either one and a whole category walks in.
Where fingerprints fail
I promised trade-offs, so here they are. Fingerprints catch fakes. They do not catch the real thing.
**A real browser is a real browser. **In one period, a swarm hit one of our stores running genuine Chrome, driven by automation. Its fingerprint was identical to that of 465 real customers' addresses on the same store. At the time we had a wall that armed itself on fingerprints when the store was under load. It did exactly what it was told: it turned away real shoppers, and it broke checkout. We removed it shortly for good. Keying on identity is a dead end when the identity is shared with your customers.
**The network can rewrite your handshake. **A few months back, one of our own team was on an a certain ISP and got refused by our own edge. The carrier was intercepting TLS, so a perfectly normal phone arrived with a handshake that did not look like a phone. Corporate proxies do the same thing. Your customers do not choose their networks.
That is exactly Cloudflare's point: fingerprints can be easily spoofed, they change frequently, and traffic keeps evolving. So on the pages shoppers browse, a fingerprint mismatch is never a verdict on its own. It is evidence. It feeds the score, along with everything else, and the score decides. In our data, some mismatched requests still pass, because nothing else about them looks wrong.
The one place a mismatch is a verdict
Stores also have machine-facing channels, where payment providers, marketplaces and stock systems talk to Magento. Honest software on those channels introduces itself honestly. It has no reason to pretend to be Chrome.
So when a client on a machine channel claims to be a browser and its connection says otherwise, there is only one explanation left (malicious). N a price scraper went after a store's product catalogue through its machine channel, dressed as Chrome. Since then that combination is refused outright.
Fingerprints are not a wall. They are one of the layers that makes lying expensive. Fake ones are definitely goes to the bin with difficult proof of work. The next part is about how all the layers add up to a decision.
Numbers in this post come from the edge logs of selected production stores we operate, over a given period — about 8.5 million requests. Store names are left out on purpose.
Next up, Part 4: How to Build a Bot Score (coming soon).
The anti-bot series: *Part 1: Overview · Part 2: OpenResty vs NGINX, Caddy and Traefik · Part 3: How to Spot a Fake Chrome · Part 4: How to Build a Bot Score (coming soon) · Part 5: Self-Hosted Proof of Work with ALTCHA (coming soon).*
StoreFrame reads the TLS and HTTP/2 fingerprint of every request at an OpenResty edge in front of every Magento store it runs, alongside scoring, proof of work and a firewall. No third party in the path.
See how StoreFrame protects Magento stores

Top comments (2)
Nice try!
This is some of the best-evidenced anti-bot writing I've read — the 86-overlap number is the whole argument in one stat, and the honesty in "Where fingerprints fail" (the automation swarm that was fingerprint-identical to 465 real customers, breaking your own checkout) is the part most vendor blog posts in this space would quietly cut.
I build RAG/LLM agent systems, and reading this raised a tension I don't think you've had to deal with yet but probably will soon: your bio says you're focused on agentic e-commerce, and the entire architecture in this post is built to catch exactly the traffic pattern a legitimate AI shopping agent produces — non-browser HTTP stack, claiming or implying browser-like behavior, hitting product/catalog endpoints programmatically. Your "machine channel = mismatch is a verdict" rule is airtight for payment/marketplace APIs that have no reason to impersonate Chrome, but an agentic checkout flow (an agent that fetches a product page, decides, then transacts, possibly over something like x402 for the payment leg) is a case where a non-browser fingerprint on a customer-facing path isn't automatically hostile anymore. Have you started thinking about a declared-agent channel — something like a signed or registered identity for agents that intentionally don't impersonate a browser, so they can skip the fingerprint scoring entirely instead of being scored as a possible scraper?
On the series itself: have you found HTTP/2 fingerprint stability to survive HTTP/3/QUIC adoption, or is that a fourth signal you're already planning for once enough traffic moves off HTTP/2?
One monetization thought, for what it's worth: the 3-signal dataset you're building (TLS + HTTP/2 + machine-channel behavior) across multiple production stores is itself a sellable asset independent of StoreFrame the hosting product — a fingerprint-signature feed or API that other Magento hosts/agencies could subscribe to, versioned as browser engines evolve, could be a second revenue line that doesn't require anyone to migrate hosting to you.
Looking forward to Part 4 (bot score) and Part 5 (ALTCHA proof-of-work) — this series is shaping up to be a genuinely useful reference.