The Akamai HTTP/2 fingerprint

A fingerprint that ignores TLS entirely. It reads the first few frames of an HTTP/2 connection — how the client configures the session, how it manages flow control, and the order it writes its pseudo-headers — and those choices identify the HTTP library as reliably as a ClientHello identifies the TLS one. Described by Akamai in a 2017 paper on passive HTTP/2 client fingerprinting.

The format

Four fields separated by pipes:

1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p

1:65536;2:0;4:6291456;6:262144  SETTINGS, in the order sent
15663105                        connection-level WINDOW_UPDATE
0                               PRIORITY frames (none here)
m,a,s,p                         pseudo-header order

SETTINGS

1:65536;2:0;4:6291456;6:262144

Every setting in the client's SETTINGS frame as id:value, joined with semicolons, in the order sent. Which settings a client bothers to send, and which it leaves at the default, is as characteristic as the values

WINDOW_UPDATE

15663105

The increment on the connection-level WINDOW_UPDATE, if the client sends one. 00 when it does not

PRIORITY

0

Each PRIORITY frame as streamId:exclusive:dependsOn:weight, joined with commas. 0 when there are none. Firefox builds an elaborate priority tree here; most clients send nothing

Pseudo-headers

m,a,s,p

The order of :method, :authority, :scheme and :path in the first request, as first letters. Chrome sends m,a,s,p; Firefox sends m,p,a,s

Why it catches people

It sits at a different layer from JA3 and JA4, and almost nobody thinks about it. A client can be made to produce a perfect Chrome ClientHello — that is a solved problem, several libraries do it — and still announce itself immediately afterwards by sending four SETTINGS where Chrome sends five, or by writing :path before :authority.

The pseudo-header order is the cleanest example. HTTP/2 requires the pseudo-headers to precede regular headers and forbids duplicates, but it does not fix their relative order, so each implementation just writes them in whatever order its code happens to. That order is stable per library and effectively invisible unless you are looking for it.

Flow control gives away as much. The connection-level WINDOW_UPDATE increment is a tuning constant chosen by whoever wrote the client, and the values in the wild cluster tightly by implementation.

What breaks it

Anything that re-frames the connection. A proxy, a CDN or a load balancer that terminates HTTP/2 and re-emits it will substitute its own SETTINGS and frame ordering, so what the origin measures is the proxy. This service terminates TLS and speaks HTTP/2 itself for exactly that reason.

Connection reuse. SETTINGS and the connection WINDOW_UPDATE appear once per connection, not once per request. A fingerprint taken from a reused connection describes the connection’s opener.

HTTP/1.1. There are no frames to read. For an HTTP/1.1 client the equivalent signal is header order and original casing, which /api/http1 returns as sent.

See your own

curl -s https://kittens.sh/api/akamai returns the fingerprint string as plain text; /api/http2 returns the frames it was built from, in wire order, so you can see which setting differs rather than just that the hash does. Related: JA4 and JA3 for the TLS layer.