Home/Blog/HTTP/2 Fingerprinting
Bot Detection

HTTP/2 Fingerprinting and Web Scraping

One layer below TLS sits the HTTP/2 frame layer. The way your client negotiates an h2 connection - its SETTINGS, flow-control window, stream priority, and pseudo-header order - is a fingerprint of its own. Here's how it works, and why it has to agree with everything else you send.

9 min read·Last updated: July 2026

Quick Answer

HTTP/2 fingerprinting reads how a client opens an h2 connection - the SETTINGS frame values, the WINDOW_UPDATE increment, stream priority, and the order of the :method, :authority, :scheme, and :path pseudo-headers. Browsers and default HTTP libraries pick different values, so a scraper whose TLS says "Chrome" but whose h2 frames say "Python" is an internal contradiction detectors catch.

  • The h2 fingerprint is passive - read from the connection, no JavaScript needed
  • TLS, HTTP/2, and header signals are cross-checked; a mismatch is itself a flag
  • Alignment means a real browser engine or curl-impersonate, plus a matching exit IP

Most guides to bot detection stop at TLS. But TLS fingerprinting (JA3/JA4) only describes the handshake. Once the encrypted tunnel is up, the client and server speak HTTP/2 - a binary protocol of frames and streams - and that conversation has its own tells. This post isolates that layer: what an HTTP/2 fingerprint is made of, why default clients look nothing like browsers, and why a mismatch between your TLS stack and your h2 behavior is one of the cleanest automation signals there is. For the broader detection picture, start with how websites detect proxies.

SETTINGS, WINDOW_UPDATE, and priority

HTTP/2 replaces plain-text requests with a binary protocol carried over a single TCP (or, for HTTP/3, QUIC) connection. When the connection opens, each side sends a SETTINGS frame declaring its parameters - header table size, whether push is enabled, the maximum number of concurrent streams, the initial flow-control window, and the maximum frame size. The exact values, and the order they appear in, are chosen by the implementation.

Two more signals ride alongside SETTINGS. The WINDOW_UPDATE frame adjusts connection-level flow control: browsers typically request a large window (on the order of many megabytes) so a page's many resources can stream in parallel, whereas default HTTP libraries tend to leave a conservative window (roughly 64 KB). And stream PRIORITY - the dependency and weight a client assigns to streams - differs sharply: Chrome-family browsers moved to the newer prioritization scheme and send little or no legacy PRIORITY data, while Firefox still builds an explicit priority tree.

SETTINGS

Which parameters are sent, their values, and their order

WINDOW_UPDATE

The flow-control increment - large for browsers, small for default clients

PRIORITY

Stream dependency and weight, or its absence

The takeaway from Akamai's research is that these choices are stable per implementation but vary across them - enough to tell one client family from another without any client-side script.

Pseudo-header ordering

In HTTP/2, the request line is gone. Instead, four pseudo-headers carry that information: :method, :authority, :scheme, and :path. The specification requires them before regular headers, but it does not fix the order among themselves - so implementations settle into a consistent, and revealing, order.

In the Akamai fingerprint notation the order is abbreviated to single letters - m for method, a for authority, s for scheme, p for path. Chrome-family browsers emit one order, Firefox another, and Safari a different one again. A client library that lists the pseudo-headers in yet another sequence stands out immediately, because no mainstream browser produces that arrangement.

Pseudo-header order is a small, invisible signal that most homegrown scrapers never think to control - which is exactly why it discriminates so well. It costs a detector nothing to read and costs a naive client its cover.

Akamai's passive h2 fingerprint and JA4H

The foundational work here is Akamai's white paper Passive Fingerprinting of HTTP/2 Clients, presented by Ory Segal and Elad Shuster at Black Hat Europe 2017. Their study analyzed a large volume of real HTTP/2 traffic and showed that distinct client families produce distinct, stable fingerprints. The format they proposed concatenates four sections: the SETTINGS parameters, the WINDOW_UPDATE value, the PRIORITY frame data, and the pseudo-header order - for example a string of the shape 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p.

The word passive is the important one: the server reads all of this straight off the connection, with no challenge and no JavaScript. Under the hood, many servers and tools build on nghttp2, the widely used HTTP/2 C library, to parse these frames. This same logic is what powers the h2 checks inside commercial anti-bot stacks - see how Akamai Bot Manager works for where it sits in the wider system.

The HTTP-layer counterpart on the request side is JA4H, part of FoxIO's JA4+ fingerprint suite. JA4H hashes request metadata - the method, HTTP version, whether a cookie and referer are present, the header count, the accept-language value, the order of the headers, and the cookie fields - into a compact identifier. Paired with JA4 (TLS) and the Akamai h2 fingerprint, it gives a detector a multi-layer profile of a single client.

The internal contradiction detectors catch

Here is the trap that catches most scrapers. A team learns that TLS fingerprinting exists, so they patch their TLS stack to look like Chrome. But the HTTP/2 layer underneath is untouched - it still emits the SETTINGS, flow-control window, and pseudo-header order of whatever library they used, such as Python's httpx or Go's net/http2.

Now the request is self-contradictory: the handshake claims a browser, the frames claim a default client, and no real Chrome ever produces that pair. A detector does not even need to know which library you used - the inconsistency alone is enough to score the session as automated. This is why anti-bot vendors cross-validate layers rather than trusting any one signal, a pattern you can see in how Cloudflare Bot Management works.

The lesson: spoofing one layer in isolation is worse than spoofing nothing, because it creates a combination that never occurs in the wild. Consistency across TLS, HTTP/2, and headers beats a perfect fake of any single layer.

Aligning every layer

There are two honest ways to make all the layers agree, and they sit at different points on the effort curve:

  • Run a real browser engine. A genuine Chromium or Firefox build produces authentic TLS, HTTP/2, and header fingerprints by construction, because it is the client it claims to be. Automation frameworks that drive a real engine inherit that consistency for free.
  • Use curl-impersonate. The curl-impersonate project (and its curl_cffi Python binding) patches curl and its h2 layer to reproduce a target browser's ClientHello and its HTTP/2 SETTINGS, window, priority, and pseudo-header order - so the network fingerprints line up with the browser being impersonated instead of with curl's defaults.

Both approaches fix the client stack. Neither one touches the network identity of your exit IP - that is a separate layer, and it has to be coherent too. If your transport itself is off, the fingerprint work is wasted; the difference between a plain HTTP proxy and a tunnelled connection matters here, which is why SOCKS5 vs HTTP proxy is worth understanding before you scale.

Where the exit IP fits

A perfectly aligned client stack still exits somewhere, and that somewhere carries a reputation. The fingerprint layers answer "does this look like a real browser?" The network layer answers "does this look like a real user's connection?" Both are scored, and both need to hold up.

A mobile proxy gives you a real 4G/5G carrier IP behind carrier-grade NAT, where thousands of genuine subscribers share the same address - which makes that address expensive for a detector to block outright. It does not change a single HTTP/2 frame you send; it changes who the network thinks you are. Pair a consistent browser or curl-impersonate stack with a carrier exit and every layer tells the same story.

New to the mechanics? Start with what is a mobile proxy for how carrier IPs and rotation actually work.

Sources

Frequently asked questions

What is an HTTP/2 fingerprint?

It is derived from how a client sets up an h2 connection: the SETTINGS frame parameters and their order, the connection-level WINDOW_UPDATE increment, any stream PRIORITY information, and the order of the pseudo-headers (:method, :authority, :scheme, :path). Those values are chosen by the implementation, so they differ between browsers and default libraries.

What is the Akamai HTTP/2 fingerprint format?

Akamai's Ory Segal and Elad Shuster described a passive format that concatenates four parts: the SETTINGS parameters as ID:Value pairs, the WINDOW_UPDATE increment, the PRIORITY frame data, and the pseudo-header order abbreviated as letters such as m,a,s,p. It is passive because a server reads it from the connection, with no JavaScript required.

What is JA4H?

JA4H is the HTTP part of FoxIO's JA4+ suite. It fingerprints an individual request from metadata such as the method, HTTP version, cookie and referer presence, the number of headers, the accept-language value, the order of the headers, and the cookie fields. It works alongside JA4 (TLS) to profile a client across layers.

Why does a Chrome TLS fingerprint with a Python HTTP/2 stack get flagged?

Because the layers contradict each other. If the TLS ClientHello matches Chrome but the HTTP/2 SETTINGS, window, and pseudo-header order match a default library such as httpx or Go's net/http2, no real browser produces that combination. Detectors cross-validate the layers, so the contradiction itself is a signal.

Can curl-impersonate match a browser's HTTP/2 fingerprint?

Yes. curl-impersonate (and the curl_cffi Python binding) patches curl and its HTTP/2 layer to send a target browser's exact TLS ClientHello and its HTTP/2 SETTINGS, window, priority, and pseudo-header order. That makes the network fingerprints line up with the browser being impersonated, which a raw HTTP client cannot do on its own.

Does a mobile proxy fix HTTP/2 fingerprinting?

No. A mobile proxy changes the network identity of the exit IP, not the h2 frames your client emits - they are separate layers. A trusted 4G/5G IP helps at the reputation layer, but your HTTP/2 and TLS fingerprints still have to look like a coherent real client. You need both.

Related Guides

Match every layer

Give your aligned stack a real carrier exit

Genuine 4G/5G mobile IPs with CGNAT trust, API rotation, and sticky sessions - the network layer your fingerprint work depends on. Test it for $5.