HTTP/2 Fingerprinting Explained: How Protocol Signals Expose Automation in 2026

HTTP/2 fingerprinting identifies clients by their SETTINGS frame, WINDOW_UPDATE, and pseudo-header order — before any HTML loads. Learn how anti-bot systems use the Akamai h2 fingerprint and JA4H to detect automation, and how to present a coherent browser fingerprint with residential proxies.

HTTP/2 Fingerprinting Explained: How Protocol Signals Expose Automation in 2026
In this article

What Is HTTP/2 Fingerprinting Explained

HTTP/2 fingerprinting is the practice of identifying clients by how they configure the HTTP/2 protocol at the binary framing layer — before any application data is exchanged. While TLS fingerprinting (JA3/JA4) captures the ClientHello packet, HTTP/2 fingerprinting inspects the SETTINGS frame, WINDOW_UPDATE frame, stream priority tree, and pseudo-header order. The result is a compact string — often called the Akamai HTTP/2 fingerprint — that distinguishes a real Chrome browser from a Python httpx client with near-certainty, before a single HTML byte is returned.

If your scraping stack sends a Chrome-shaped TLS ClientHello but an httpx-shaped h2 SETTINGS frame, anti-bot systems don't need to evaluate your JavaScript or check your CAPTCHA token. The protocol mismatch alone is enough to assign a maximum bot score before any HTML loads.

The H2 Settings Frame: What Gets Fingerprinted

HTTP/2, defined in RFC 9113, requires every connection to begin with a SETTINGS frame carrying key-value pairs that configure the connection. Real browsers send a specific, consistent set of these parameters — and most HTTP client libraries send a different, much smaller set.

The parameters that matter for fingerprinting:

  • HEADER_TABLE_SIZE (ID 1): The HPACK dynamic header table size. Chrome sends 65536 bytes; the Python h2 library (used by httpx) defaults to 4096 bytes.
  • ENABLE_PUSH (ID 2): Whether server push is enabled. Chrome sends 0 (disabled since Chrome 76); many libraries leave it at the spec default of 1 or omit it.
  • INITIAL_WINDOW_SIZE (ID 4): The flow-control window for streams. Chrome sends 6291456 bytes (6 MB); httpx defaults to 65535 bytes (64 KB).
  • MAX_HEADER_LIST_SIZE (ID 6): Maximum acceptable header list size. Chrome sends 262144 bytes (256 KB); most libraries don't send this parameter at all.

Beyond SETTINGS, two additional signals are captured:

  • WINDOW_UPDATE: After the SETTINGS frame, the client sends a connection-level WINDOW_UPDATE. Chrome sends 15663105 — a value so specific that its absence is itself a signal.
  • Pseudo-header order: HTTP/2 uses pseudo-headers (:method, :authority, :scheme, :path). Chrome sends them as m,a,s,p. Some clients reorder these, creating an immediate mismatch.

The Akamai h2 fingerprint concatenates all of this into a compact string:

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

# httpx (Python h2 library) fingerprint
1:4096|65535|0|m,a,s,p

The difference is stark. The httpx fingerprint is missing four of Chrome's six settings, has a 16× smaller header table, and a 96× smaller initial window. No heuristic analysis needed — a simple string comparison flags this as non-browser.

JA4H: The Application-Layer Companion

While the Akamai h2 fingerprint captures the protocol layer, JA4H — part of the JA4 suite by FoxIO — captures the HTTP application layer. JA4H encodes the HTTP method, version, cookie presence, header count, and sorted header names into a fingerprint string like ge20cn05acijefhmsu.

JA4H and the h2 fingerprint are complementary. A sophisticated anti-bot system checks both: the h2 fingerprint identifies the protocol implementation, while JA4H identifies the HTTP header profile. If either doesn't match the claimed browser, the request is flagged.

Why Protocol Mismatches Trigger Max Bot Scores

Here's the scenario that gets you blocked before your request reaches the application layer:

  1. Your TLS ClientHello matches Chrome 148 (JA4 = t13d1516h2_...), because you configured your TLS library to impersonate Chrome's cipher order and extensions.
  2. Your HTTP/2 SETTINGS frame still carries h2 library defaults: HEADER_TABLE_SIZE=4096, INITIAL_WINDOW_SIZE=65535, no MAX_HEADER_LIST_SIZE.
  3. The anti-bot system compares your JA4 (says "Chrome 148") against your h2 fingerprint (says "Python h2 library"). They don't agree.
  4. You're assigned a maximum bot score — often 90+ out of 100. No JavaScript challenge is issued. No CAPTCHA is shown. The connection is rejected or returned a 403.

This is why simply patching your TLS fingerprint (using a library like tls-client or configuring BoringSSL cipher order) is insufficient. The anti-bot vendor doesn't need JavaScript execution to catch you — the protocol layer already told them you're lying about your identity.

TLS and HTTP/2 Coherence: The Full Stack Problem

Anti-bot detection is a coherence check, not a single-signal test. The system asks: "Do all signals agree?" If your JA3/JA4 says Chrome, your h2 fingerprint says Chrome, your JA4H says Chrome, and your User-Agent says Chrome — you pass. If any one signal disagrees, you fail.

LayerFingerprintWhat It CapturesExample (Chrome 148)
TLSJA3 / JA4ClientHello cipher order, extensions, curvest13d1516h2_...
HTTP/2Akamai h2 fingerprintSETTINGS, WINDOW_UPDATE, priority, pseudo-header order1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p
HTTP headersJA4HMethod, version, header count, header namesge20cn05acijefhmsu
JavaScriptCanvas / WebGL / navigatorBrowser API fingerprints, feature detectionChrome-specific APIs present

Most Python and Node.js HTTP clients leak mismatched signals at the h2 layer:

  • httpx (Python): Uses the h2 library, which sends HEADER_TABLE_SIZE=4096 and omits most Chrome settings. The TLS layer won't match the h2 layer.
  • requests (Python): Doesn't support HTTP/2 at all. If your TLS ClientHello advertises h2 via ALPN but requests falls back to HTTP/1.1, that creates a TLS/h2 mismatch.
  • Node.js http2 module: Sends its own default SETTINGS (HEADER_TABLE_SIZE=4096, ENABLE_PUSH=1), which don't match any real browser.
  • undici (Node.js): Uses Node's built-in h2 implementation with similar non-browser defaults.
  • Go net/http2: Sends HEADER_TABLE_SIZE=4096 and INITIAL_WINDOW_SIZE=65535 — the same giveaway as httpx.

Why Residential Proxies Are Still Required

Even with a perfectly coherent fingerprint — matching TLS, h2, and HTTP headers — you still need the right IP. Protocol spoofing alone fails because anti-bot systems apply IP-reputation scoring independently of protocol analysis.

Here's how it works in practice:

  1. Your h2 fingerprint matches Chrome perfectly. Your JA4 matches Chrome. Your headers are indistinguishable from a real browser.
  2. Your request comes from a datacenter IP range belonging to AWS, DigitalOcean, or a known VPS provider.
  3. The anti-bot system's IP-reputation database flags the IP as datacenter. Datacenter IPs carry a high baseline risk score — often 70–90 out of 100 before any other signal is evaluated.
  4. Your coherent fingerprint brings the score down, but not enough. A datacenter IP with a perfect browser fingerprint is still more suspicious than a residential IP with a slightly imperfect one.

Residential proxies solve this by routing your traffic through real ISP-assigned IP addresses. The IP-reputation check returns a low risk score (0–20), and your coherent fingerprint does the rest. This is why combining protocol-level spoofing with residential proxy locations is the standard approach for serious scraping operations. For more on this, see our web scraping use case guide.

Mobile proxies offer an even lower baseline risk score, as carrier IPs are treated as highly trusted by most reputation databases. However, residential proxies provide a better balance of cost, availability, and geographic coverage for most use cases.

Practical Implementation: Coherent Fingerprints with ProxyHat

There are two legitimate approaches to presenting a coherent h2 fingerprint: using a browser-impersonation library that reproduces Chrome's exact h2 settings, or using a real browser routed through a proxy. Full configuration details are available in the ProxyHat documentation.

Approach 1: curl_cffi with ProxyHat Residential Proxies

The curl_cffi library wraps libcurl-impersonate, which patches curl's TLS and HTTP/2 implementation to match real browsers byte-for-byte. When you specify impersonate="chrome", it sends Chrome's exact TLS ClientHello and Chrome's exact h2 SETTINGS frame — including the correct WINDOW_UPDATE and pseudo-header order.

from curl_cffi import requests

# ProxyHat residential proxy — US exit
proxies = {
    "http": "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080",
    "https": "http://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
}

# Impersonate Chrome 148 — TLS + h2 + headers all coherent
response = requests.get(
    "https://httpbin.org/headers",
    impersonate="chrome",
    proxies=proxies,
    timeout=30
)

print(f"Status: {response.status_code}")
print(response.text)

For sticky sessions (useful for multi-page flows that need the same IP):

from curl_cffi import requests

proxies = {
    "http": "http://user-session-checkout01-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080",
    "https": "http://user-session-checkout01-country-US:YOUR_PASSWORD@gate.proxyhat.com:8080"
}

response = requests.get(
    "https://example.com/login",
    impersonate="chrome",
    proxies=proxies
)

Approach 2: Real Browser via Playwright + ProxyHat

For targets with advanced JavaScript challenges (Cloudflare Turnstile, Akamai Bot Manager, DataDome), a real browser is the most reliable option. Playwright launches an actual Chromium instance, which naturally sends the correct h2 SETTINGS frame.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch(
        headless=True,
        proxy={
            "server": "http://gate.proxyhat.com:8080",
            "username": "user-country-US",
            "password": "YOUR_PASSWORD"
        }
    )
    page = browser.new_page()
    page.goto("https://example.com")
    content = page.content()
    print(content[:500])
    browser.close()

The trade-off: a real browser is 5–10× slower than curl_cffi and uses significantly more memory. For high-volume SERP tracking or price monitoring, curl_cffi is the better choice. For targets that require JavaScript rendering, use a browser.

SOCKS5 Alternative

If you need SOCKS5 (e.g., for certain tunneling setups), ProxyHat supports SOCKS5 on port 1080:

from curl_cffi import requests

proxies = {
    "http": "socks5://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:1080",
    "https": "socks5://user-country-US:YOUR_PASSWORD@gate.proxyhat.com:1080"
}

response = requests.get(
    "https://httpbin.org/ip",
    impersonate="chrome",
    proxies=proxies
)
print(response.json())

Common Mistakes and Edge Cases

Mistake 1: Patching TLS but Not h2

The most common mistake is using a TLS-impersonation library without also fixing the h2 layer. Your JA4 says Chrome, but your h2 SETTINGS say Python. The mismatch is immediately detectable by any system that checks both layers — and most modern anti-bot systems do.

Mistake 2: Using HTTP/1.1 with an h2-Advertised ALPN

If your TLS ClientHello advertises h2 in the ALPN extension but your client only speaks HTTP/1.1, the server may reject the connection or flag it as anomalous. Ensure your client actually implements HTTP/2 when you advertise it via ALPN.

Mistake 3: Inconsistent Pseudo-Header Order

Some custom h2 implementations send pseudo-headers in the order m,s,a,p or m,p,a,s instead of Chrome's m,a,s,p. This alone can trigger detection on strict anti-bot systems that include pseudo-header order in their fingerprint string.

Mistake 4: Ignoring WINDOW_UPDATE

The connection-level WINDOW_UPDATE value is part of the Akamai fingerprint. If your client sends a different value than the browser you're impersonating, the fingerprint won't match. Chrome's 15663105 is distinctive; sending 65535 instead is a dead giveaway.

Mistake 5: Using Datacenter Proxies with a Perfect Fingerprint

A perfect browser fingerprint from a datacenter IP is more suspicious than an imperfect fingerprint from a residential IP. Always pair protocol-level spoofing with residential proxy infrastructure to pass IP-reputation scoring.

HTTP/2 fingerprinting research and browser-impersonation techniques have legitimate applications:

  • Authorized security testing: Penetration testers evaluating anti-bot protections for clients who own the target infrastructure.
  • Competitive price monitoring: Collecting publicly available pricing data from e-commerce sites, in compliance with their terms of service.
  • SERP tracking: Monitoring search engine rankings for SEO purposes — a well-established, legitimate use case.
  • Academic research: Studying anti-bot systems, web measurement, and protocol analysis.

However, these techniques must not be used for fraud, credential stuffing, circumventing access controls, or violating the Computer Fraud and Abuse Act (CFAA) in the US or the General Data Protection Regulation (GDPR) in the EU. Always review the target's Terms of Service and robots.txt. If you're unsure whether your use case is legal, consult with a qualified attorney. ProxyHat's infrastructure is designed for legitimate automation, research, and monitoring — we do not support or condone use of our proxies for fraudulent activity or violations of applicable law.

Key Takeaways

  • HTTP/2 fingerprinting captures the SETTINGS frame, WINDOW_UPDATE, stream priority, and pseudo-header order — all sent before any HTML loads.
  • The Akamai h2 fingerprint is a compact string like 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p that uniquely identifies browser implementations.
  • JA4H complements the h2 fingerprint by capturing the HTTP header profile. Both must agree with your TLS fingerprint (JA3/JA4).
  • Most Python and Node.js clients (httpx, Node http2, Go net/http2) leak non-browser h2 settings frames with HEADER_TABLE_SIZE=4096 and INITIAL_WINDOW_SIZE=65535.
  • Protocol spoofing alone is insufficient — residential proxies are required to pass IP-reputation scoring.
  • Use curl_cffi with impersonate="chrome" for a coherent TLS+h2 fingerprint, routed through ProxyHat residential exits at gate.proxyhat.com:8080.
  • For JavaScript-heavy targets, use a real browser (Playwright) through ProxyHat — it naturally sends the correct h2 SETTINGS.

Frequently asked questions

What is HTTP/2 fingerprinting?

HTTP/2 fingerprinting identifies clients by their binary protocol configuration — specifically the SETTINGS frame (HEADER_TABLE_SIZE, INITIAL_WINDOW_SIZE, MAX_CONCURRENT_STREAMS, MAX_HEADER_LIST_SIZE), the connection-level WINDOW_UPDATE value, stream priority, and pseudo-header order. Anti-bot systems like Akamai compile these into a compact fingerprint string (e.g., 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p) that distinguishes real browsers from automation libraries before any HTML is returned.

Why does HTTP/2 fingerprinting matter for proxy users?

Even if your TLS fingerprint (JA3/JA4) perfectly matches Chrome, a mismatched h2 SETTINGS frame immediately exposes automation. For example, if your JA4 claims Chrome 148 but your h2 fingerprint sends HEADER_TABLE_SIZE=4096 (the httpx default instead of Chrome's 65536), anti-bot systems assign a maximum bot score before any HTML loads. Proxy users must ensure their TLS, h2, and HTTP header fingerprints all agree to avoid detection.

Which proxy type works best for HTTP/2 fingerprinting?

Residential proxies are the best choice for HTTP/2 fingerprinting because they provide low IP-reputation risk scores (0–20 out of 100) compared to datacenter IPs (70–90). Even with a perfectly coherent h2 and TLS fingerprint, a datacenter IP triggers high baseline suspicion. Mobile proxies offer the lowest risk scores but at higher cost. Residential proxies through a provider like ProxyHat (gate.proxyhat.com:8080) provide the best balance of cost, reliability, and geographic coverage for most legitimate automation use cases.

How do you avoid blocks when implementing HTTP/2 fingerprinting?

To avoid blocks, ensure full stack coherence: use curl_cffi with impersonate='chrome' to match Chrome's exact TLS ClientHello and h2 SETTINGS frame simultaneously, route through residential proxies to pass IP-reputation scoring, and ensure your JA4H header profile matches the browser you're impersonating. Never patch TLS without also fixing h2 settings, avoid HTTP/1.1 fallback when ALPN advertises h2, and always use residential rather than datacenter proxies. For JavaScript-heavy targets, use a real browser via Playwright through your proxy.

What is the Akamai h2 fingerprint format?

The Akamai h2 fingerprint format is a compact string combining four components separated by pipes: SETTINGS frame parameters (as id:value pairs joined by semicolons), the connection-level WINDOW_UPDATE value, stream priority information, and pseudo-header order. For example, Chrome's fingerprint is 1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p, where 1=HEADER_TABLE_SIZE, 2=ENABLE_PUSH, 4=INITIAL_WINDOW_SIZE, 6=MAX_HEADER_LIST_SIZE, and m,a,s,p represents :method, :authority, :scheme, :path order.

Test your proxies against real anti-bot defenses

Free proxy checker — latency, anonymity and block signals in one click.

Run a free check
← Back to Blog