Markdown source

HTTP/2 Fingerprinting: The Akamai Format Markdown source

Readable source view for humans. The raw Markdown endpoint remains available for crawlers and agent readers.

---
title: "HTTP/2 Fingerprinting: The Akamai Format"
description: "How the Akamai HTTP/2 fingerprint works — SETTINGS, WINDOW_UPDATE, PRIORITY, and pseudo-header order: the layer after JA4 that default clients fail."
kind: article
maturity: budding
confidence: high
origin: ai-drafted
author: "Agent"
directedBy: "krow"
tags: [http2, fingerprinting, bot-detection, anti-detection, networking]
published: 2026-05-29
modified: 2026-06-25
wordCount: 1055
readingTime: 5
prerequisites: [bot-detection-2026]
related: [akamai-bot-manager-2026, bot-detection-2026, ja4-plus-fingerprint-suite, ja4t-tcp-fingerprinting, ja4-vs-ja3, tls-fingerprinting-curl-cffi]
url: https://krowdev.com/article/http2-fingerprinting-akamai/
---
## Agent Context

- Canonical: https://krowdev.com/article/http2-fingerprinting-akamai/
- Markdown: https://krowdev.com/article/http2-fingerprinting-akamai.md
- Full corpus: https://krowdev.com/llms-full.txt
- Kind: article
- Maturity: budding
- Confidence: high
- Origin: ai-drafted
- Author: Agent
- Directed by: krow
- Published: 2026-05-29
- Modified: 2026-06-25
- Words: 1055 (5 min read)
- Tags: http2, fingerprinting, bot-detection, anti-detection, networking
- Prerequisites: bot-detection-2026
- Related: akamai-bot-manager-2026, bot-detection-2026, ja4-plus-fingerprint-suite, ja4t-tcp-fingerprinting, ja4-vs-ja3, tls-fingerprinting-curl-cffi
- Content map:
  - h2: The format
  - h3: SETTINGS
  - h3: WINDOW_UPDATE
  - h3: PRIORITY
  - h3: Pseudo-header order
  - h2: The real values
  - h2: Why it's a strong signal
  - h2: What this means for impersonation
  - h2: HTTP/3 moves the surface
  - h2: Sources
- Crawl policy: same canonical content is exposed through HTML, Markdown, and llms-full; no crawler-specific content gate.

A [JA4 TLS fingerprint](/article/ja4-vs-ja3/) 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](/article/bot-detection-2026/#layer-2-http2-settings): JA4 is the TLS handshake; this is the HTTP/2 setup that immediately follows on the same connection. In product context, [Akamai Bot Manager](/article/akamai-bot-manager-2026/) 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](/article/ja4-plus-fingerprint-suite/).

## The format

Akamai's [Black Hat EU 2017 research](https://www.blackhat.com/docs/eu-17/materials/eu-17-Shuster-Passive-Fingerprinting-Of-HTTP2-Clients-wp.pdf) defined a fingerprint from four client-controlled parts of the HTTP/2 connection preface, joined by `|`:

```text
SETTINGS | WINDOW_UPDATE | PRIORITY | PSEUDO_HEADER_ORDER
```

A real Chrome 144 fingerprint:

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

Each 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](https://datatracker.ietf.org/doc/html/rfc9113#section-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](https://datatracker.ietf.org/doc/html/rfc8441)) and `9` (`NO_RFC7540_PRIORITIES`, [RFC 9218](https://datatracker.ietf.org/doc/html/rfc9218)) — 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](https://datatracker.ietf.org/doc/html/rfc9218) 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](/article/ja4-vs-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](/note/tls-fingerprinting-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](/article/tls-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](https://www.blackhat.com/docs/eu-17/materials/eu-17-Shuster-Passive-Fingerprinting-Of-HTTP2-Clients-wp.pdf) — the original Black Hat EU 2017 white paper defining the fingerprint.
- [RFC 9113 — HTTP/2](https://datatracker.ietf.org/doc/html/rfc9113) — SETTINGS frame, flow control, and pseudo-headers.
- [RFC 9218 — Extensible Prioritization Scheme for HTTP](https://datatracker.ietf.org/doc/html/rfc9218) — the `NO_RFC7540_PRIORITIES` setting (`id 9`).
- [Scrapfly — HTTP/2 and HTTP/3 fingerprinting](https://scrapfly.io/blog/posts/http2-http3-fingerprinting-guide) — per-browser values and the extended fingerprint format.
- [lwthiker — HTTP/2 fingerprinting](https://lwthiker.com/networks/2022/06/17/http2-fingerprinting.html) — pseudo-header orders by client, from the curl-impersonate author.