HTTP/2 Fingerprinting: The Akamai Format
How the Akamai HTTP/2 fingerprint works — SETTINGS, WINDOW_UPDATE, PRIORITY, and pseudo-header order: the layer after JA4 that default clients fail.
On this page
A JA4 TLS fingerprint identifies the client from its ClientHello. But the handshake isn’t the last thing the server sees before your request — once TLS completes, the client opens an HTTP/2 connection, and the very first frames it sends are just as identifying. The Akamai HTTP/2 fingerprint captures that connection preface. A real Chrome and a requests-with-a-Chrome-User-Agent diverge here before a single header is read.
This sits one layer above JA4 in the bot-detection stack: JA4 is the TLS handshake; this is the HTTP/2 setup that immediately follows on the same connection. In product context, Akamai Bot Manager uses this kind of protocol evidence alongside transparent request checks, active browser checks, behavioral detection, reputation, and response strategy. For the full cross-layer map — JA4S, JA4H, JA4X, JA4L, JA4SSH, and JA4T — see the JA4+ fingerprint suite.
The format
Akamai’s Black Hat EU 2017 research defined a fingerprint from four client-controlled parts of the HTTP/2 connection preface, joined by |:
SETTINGS | WINDOW_UPDATE | PRIORITY | PSEUDO_HEADER_ORDERA real Chrome 144 fingerprint:
1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,pEach field is a deliberate implementation choice the client makes the same way every time — which is exactly what makes the concatenation a stable identifier.
SETTINGS
The client’s opening SETTINGS frame, as id:value pairs in send order, semicolon-separated. The standard parameter IDs (RFC 9113 §6.5.2):
| ID | Name | What it controls |
|---|---|---|
1 | HEADER_TABLE_SIZE | HPACK dynamic table size |
2 | ENABLE_PUSH | Server push allowed (browsers send 0) |
3 | MAX_CONCURRENT_STREAMS | Parallel streams the peer may open |
4 | INITIAL_WINDOW_SIZE | Per-stream flow-control window |
5 | MAX_FRAME_SIZE | Largest frame the client will accept |
6 | MAX_HEADER_LIST_SIZE | Largest header block the client will accept |
Newer clients also send extension IDs — 8 (ENABLE_CONNECT_PROTOCOL, RFC 8441) and 9 (NO_RFC7540_PRIORITIES, RFC 9218) — and Safari’s 9:1 is one of the clearest tells in its fingerprint.
The set of IDs sent, their values, and their order all vary by client. Chrome sends INITIAL_WINDOW_SIZE of 6,291,456 (6 MiB); Firefox sends 131,072 (128 KiB) — a 48× difference in one field.
WINDOW_UPDATE
A single integer: the increment in the client’s first connection-level WINDOW_UPDATE frame, or 0 if none was sent. Chrome sends 15663105, Firefox 12517377, Safari 10420225. These are fixed per client.
PRIORITY
Zero or more PRIORITY tuples, each StreamID:Exclusive:DependentStreamID:Weight, comma-separated — or 0 if the client sends none. Chrome sends none (0); Firefox historically builds a priority tree (e.g. 3:0:0:201), which is itself a strong Firefox signal. With RFC 9218 deprecating the RFC 7540 priority scheme, newer clients increasingly send none.
Pseudo-header order
The cheapest, highest-signal field. HTTP/2 carries the request line as four pseudo-headers — :method, :authority, :scheme, :path — and the order the client emits them is hardcoded per implementation, encoded as the first letters (m, a, s, p):
| Client | Pseudo-header order | Code |
|---|---|---|
| Chrome / Chromium | :method, :authority, :scheme, :path | m,a,s,p |
| Firefox | :method, :path, :authority, :scheme | m,p,a,s |
| Safari | :method, :scheme, :path, :authority | m,s,p,a |
| curl (default) | :method, :path, :scheme, :authority | m,p,s,a |
The last row is the point: a default HTTP client’s pseudo-header order matches no browser. This single field flags a connection as automated before the request headers are even parsed — and unlike a User-Agent string, you can’t fix it by setting a header.
The real values
Putting the fields together, here’s what each browser actually emits:
| Client | Akamai HTTP/2 fingerprint |
|---|---|
| Chrome | 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p |
| Firefox | 1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s |
| Safari | 2:0;3:100;4:2097152;9:1|10420225|0|m,s,p,a |
Three browsers, three distinct SETTINGS sets, three WINDOW_UPDATE values, three pseudo-header orders. Safari is the odd one out twice over: it omits HEADER_TABLE_SIZE, and it sends 9:1 to opt out of RFC 7540 priorities.
Why it’s a strong signal
It’s stable. A client’s HTTP/2 stack changes far less often than its TLS parameters. Chrome’s SETTINGS and pseudo-header order have held across many major versions, so a detection vendor can match against a fixed value without the per-version churn that plagued JA3.
It’s early and unspoofable-by-header. The fingerprint is set by the HTTP/2 library, not by request headers. You can put any User-Agent you like on the request; if your client library emits curl’s m,p,s,a pseudo-header order, you’ve already lost.
It composes with the other layers. Anti-bot systems check for cross-layer consistency. A request whose JA4 says Chrome but whose HTTP/2 fingerprint says Go net/http is more suspicious than one that gets both slightly wrong — the two layers disagree about what the client is. JA4 + HTTP/2 + header order have to tell the same story.
What this means for impersonation
This is why a browser User-Agent on requests or stock curl doesn’t work: those clients send their own HTTP/2 fingerprint, which no browser produces. A library like curl_cffi replays the target browser’s SETTINGS frame, WINDOW_UPDATE, and pseudo-header order alongside its TLS handshake — getting both the JA4 and the HTTP/2 layer right at once. The impersonation library comparison covers which libraries replay the HTTP/2 layer faithfully and which only handle TLS.
You can read your own client’s HTTP/2 fingerprint at services like tls.peet.ws — the same probe that surfaces your JA4 also reports the Akamai HTTP/2 hash, which is the cheapest way to confirm an impersonation library replayed this layer before you rely on it.
HTTP/3 moves the surface
HTTP/3 runs over QUIC, not TCP+TLS+HTTP/2, so the fingerprint surface shifts again: the SETTINGS-frame and pseudo-header signals largely carry over, but they now sit alongside QUIC transport parameters and the QUIC initial packet. The principle is unchanged — every protocol layer the client configures is a layer it can be identified by.
Sources
- Akamai — Passive Fingerprinting of HTTP/2 Clients — the original Black Hat EU 2017 white paper defining the fingerprint.
- RFC 9113 — HTTP/2 — SETTINGS frame, flow control, and pseudo-headers.
- RFC 9218 — Extensible Prioritization Scheme for HTTP — the
NO_RFC7540_PRIORITIESsetting (id 9). - Scrapfly — HTTP/2 and HTTP/3 fingerprinting — per-browser values and the extended fingerprint format.
- lwthiker — HTTP/2 fingerprinting — pseudo-header orders by client, from the curl-impersonate author.