| name | http2-header-impersonation |
| description | Spoof HTTP/2 SETTINGS frames and pseudo-header order per browser profile. |
| version | 1.0.0 |
| author | uphiago |
| license | MIT |
| platforms | ["linux"] |
| compatibility | Requires python3, pip |
| metadata | {"tags":["recon","HTTP2","headers","fingerprint","impersonation","browser","JA4"],"category":"recon","related_skills":["tls-fingerprint-impersonation","stealth-browser-launch"]} |
HTTP/2 Header Impersonation
Spoof HTTP/2 SETTINGS frames, pseudo-header ordering, and browser-specific HTTP headers to match real browsers at the protocol level. Targets the detection gap between TLS fingerprinting (ClientHello) and JavaScript fingerprinting — the HTTP/2 connection setup and header structure that anti-bot systems analyze. Matches Chrome, Firefox, Safari iOS, and OkHttp (Android) profiles with exact window sizes, header list limits, and header ordering.
When to Use
- TLS fingerprint is correct but target still detects non-browser HTTP behavior.
- Target uses HTTP/2-specific detection (SETTINGS frame analysis, pseudo-header order).
- Need browser-accurate
sec-ch-ua, Accept, Accept-Encoding, Priority, and sec-fetch-* headers.
- Mobile API endpoints require Android OkHttp header profiles.
- Combining with TLS impersonation for a complete network-level browser profile.
Prerequisites
terminal tool with python3.
pip install impit (includes HTTP/2 impersonation via patched h2 library).
- Or standalone: use the header lists below to configure curl/httpx manually.
Quick Detection
python3 -c "
import requests
# curl default headers
r1 = requests.get('https://target.com')
print(f'Default: {r1.status_code}')
# Browser-like headers
r2 = requests.get('https://target.com', headers={
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.9',
'Accept-Encoding': 'gzip, deflate, br, zstd',
'sec-ch-ua': '\"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\"',
'sec-ch-ua-platform': '\"Windows\"',
'sec-ch-ua-mobile': '?0',
})
print(f'Browser: {r2.status_code}')
"
Procedure
Phase 1 — Full Browser Profile via impit
The impit library applies HTTP/2 SETTINGS, pseudo-header order, and HTTP headers from a single profile:
from impit import Impit
impit = (
Impit.builder()
.with_fingerprint("chrome142")
.build()
)
response = impit.get("https://target.com")
Phase 2 — Manual Header Configuration
For curl/httpx without impit, configure headers manually to match browser profiles:
Chrome 142 Desktop Headers
curl -sk "https://target.com" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36" \
-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" \
-H "Accept-Language: en-US,en;q=0.9" \
-H "Accept-Encoding: gzip, deflate, br, zstd" \
-H "sec-ch-ua: \"Chromium\";v=\"142\", \"Google Chrome\";v=\"142\", \"Not?A_Brand\";v=\"99\"" \
-H "sec-ch-ua-arch: \"x86\"" \
-H "sec-ch-ua-bitness: \"64\"" \
-H "sec-ch-ua-full-version-list: \"Chromium\";v=\"142.0.7444.176\", \"Google Chrome\";v=\"142.0.7444.176\", \"Not?A_Brand\";v=\"99.0.0.0\"" \
-H "sec-ch-ua-mobile: ?0" \
-H "sec-ch-ua-model: \"\"" \
-H "sec-ch-ua-platform: \"Windows\"" \
-H "sec-ch-ua-platform-version: \"15.0.0\"" \
-H "sec-ch-ua-wow64: ?0" \
-H "sec-fetch-dest: document" \
-H "sec-fetch-mode: navigate" \
-H "sec-fetch-site: none" \
-H "sec-fetch-user: ?1" \
-H "Upgrade-Insecure-Requests: 1" \
-H "Priority: u=0, i"
Firefox 144 Desktop Headers
curl -sk "https://target.com" \
-H "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:144.0) Gecko/20100101 Firefox/144.0" \
-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8" \
-H "Accept-Language: en-US,en;q=0.5" \
-H "Accept-Encoding: gzip, deflate, br, zstd" \
-H "sec-ch-ua: \"Firefox\";v=\"144\", \"Not?A_Brand\";v=\"99\"" \
-H "sec-ch-ua-mobile: ?0" \
-H "sec-ch-ua-platform: \"Windows\"" \
-H "sec-fetch-dest: document" \
-H "sec-fetch-mode: navigate" \
-H "sec-fetch-site: none" \
-H "sec-fetch-user: ?1" \
-H "TE: trailers" \
-H "Upgrade-Insecure-Requests: 1" \
-H "Priority: u=0, i"
Safari iOS 18 Headers
curl -sk "https://target.com" \
-H "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_7 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.7 Mobile/15E148 Safari/604.1" \
-H "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" \
-H "Accept-Language: en-US,en;q=0.9" \
-H "Accept-Encoding: gzip, deflate, br" \
-H "sec-fetch-dest: document" \
-H "sec-fetch-mode: navigate" \
-H "sec-fetch-site: none"
OkHttp 4 (Android) Headers
curl -sk "https://target.com" \
-H "User-Agent: okhttp/4.12.0" \
-H "Accept: application/json, text/plain, */*" \
-H "Accept-Language: en-US,en;q=0.9" \
-H "Accept-Encoding: gzip, deflate, br" \
-H "Connection: Keep-Alive"
Phase 3 — HTTP/2 SETTINGS Frame Spoofing
HTTP/2 SETTINGS frames are sent during connection setup. curl doesn't expose them — use impit or a patched HTTP client:
from impit import Impit
impit = Impit.builder().with_fingerprint("chrome142").build()
response = impit.get("https://target.com")
Phase 4 — Pseudo-Header Order
Browsers send HTTP/2 pseudo-headers in specific orders. This is enforced at the h2 library level:
| Browser | Pseudo-Header Order |
|---|
| Chrome | :method, :authority, :scheme, :path, :protocol, :status |
| Firefox | :method, :path, :authority, :scheme, :protocol, :status |
| Safari iOS | :method, :scheme, :authority, :path, :protocol, :status |
| OkHttp | :method, :path, :authority, :scheme, :protocol, :status |
Detection: Some reverse proxies inspect pseudo-header order to distinguish browsers. Firefox places :path before :authority — a unique fingerprint. Safari places :scheme second — another differentiator.
Phase 5 — Header Prioritization
Custom headers take priority over fingerprint defaults to avoid conflicts:
impit = Impit.builder().with_fingerprint("chrome142").build()
response = impit.get("https://target.com", headers={
"Authorization": "Bearer custom-token",
"X-Custom-Header": "value",
})
Phase 6 — Accept-Encoding Strategy
Browser Accept-Encoding values differ significantly:
| Browser | Accept-Encoding |
|---|
| Chrome 142 | gzip, deflate, br, zstd |
| Chrome 100-116 | gzip, deflate, br |
| Firefox | gzip, deflate, br, zstd |
| Safari iOS | gzip, deflate, br |
| OkHttp 4 | gzip, deflate, br |
Detection: Some CDNs return different content based on Accept-Encoding (brotli vs zstd capability profiling). Match encoding capabilities to your target browser.
Pitfalls
- curl cannot spoof HTTP/2 SETTINGS frames. Only use curl-based headers for HTTP/1.1 targets or when TLS fingerprinting is the primary concern, not HTTP/2.
- Header order matters in HTTP/2. Some detectors check the order of header fields, not just their presence.
sec-ch-ua must match User-Agent. Using Chrome headers with Firefox UA creates an inconsistency that detectors flag.
sec-ch-ua format is version-specific. Chrome 100+ uses different brand strings than Chrome 124+.
- Mobile headers without TLS matching is detectable. Using Safari iOS headers over a desktop TLS fingerprint is flagged.
- Custom headers are deduplicated case-insensitively. Adding
User-Agent as custom overrides the fingerprint's User-Agent.
Verification
- Test against
https://httpbin.org/headers — verify all sent headers match the intended browser profile.
- For HTTP/2 SETTINGS verification, use
https://nghttp2.org/httpbin/headers or a local nghttp2 server.
- Compare response size/content between browser-impostor and real browser — identical responses indicate the headers are accepted.
- Check Cloudflare
cf-ja4 header — it encodes HTTP/2 fingerprint along with TLS.
Related Skills
tls-fingerprint-impersonation — TLS ClientHello and JA3/JA4 fingerprint spoofing.
stealth-browser-launch — Full browser automation with C++ fingerprint patches.