Every Byte of your Request Indistinguishable from Chrome.
📖 Full documentation at httpcloak.dev
Bot detection doesn't just check your User-Agent anymore.
It fingerprints your TLS handshake. Your HTTP/2 frames. Your QUIC parameters. The order of your headers. Whether your SNI is encrypted.
One mismatch = blocked.
importhttpcloakr=httpcloak.get("https://target.com", preset="chrome-latest")That's it. Full browser transport layer fingerprint.
|
|
|
┌─────────────────────────────────┐
│ ECH (Encrypted Client Hello) │
├─────────────────────────────────┤
│ WITHOUT: sni=plaintext │
│ WITH: sni=encrypted + │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ HTTP/3 Fingerprint Match │
├─────────────────────────────────┤
│ Protocol: h3 + │
│ QUIC Version: 1 + │
│ Transport Params: + │
│ GREASE Frames: + │
└─────────────────────────────────┘
┌────────────────────────────────┬────────────────────────────────┐
│ BOTH LIBRARIES │ HTTPCLOAK ONLY │
├────────────────────────────────┼────────────────────────────────┤
│ │ │
│ + TLS fingerprint (JA3/JA4) │ + HTTP/3 fingerprinting │
│ + HTTP/2 fingerprint │ + ECH (encrypted SNI) │
│ + Post-quantum TLS │ + MASQUE proxy │
│ + Bot score: 99 │ + Domain fronting │
│ │ + Certificate pinning │
│ │ + Go, Python, Node.js, C# │
│ │ │
└────────────────────────────────┴────────────────────────────────┘
pip install httpcloak # Python
npm install httpcloak # Node.js
go get github.com/sardanioss/httpcloak # Go
dotnet add package HttpCloak # C#importhttpcloak# Simple requestr=httpcloak.get("https://example.com", preset="chrome-latest")
print(r.status_code, r.protocol)
# POST with JSONr=httpcloak.post("https://httpbin.org/post",
json={"key": "value"},
preset="chrome-latest"
)
# Custom headersr=httpcloak.get("https://httpbin.org/headers",
headers={"X-Custom": "value"},
preset="chrome-latest"
)import (
"context""github.com/sardanioss/httpcloak/client"
)
// Simple requestc:=client.NewClient("chrome-latest")
deferc.Close()
resp, _:=c.Get(ctx, "https://example.com", nil)
body, _:=resp.Text()
fmt.Println(resp.StatusCode, resp.Protocol)
// POST with JSONjsonBody:= []byte(`{"key": "value"}`)
resp, _=c.Post(ctx, "https://httpbin.org/post",
bytes.NewReader(jsonBody),
map[string][]string{"Content-Type": {"application/json"}},
)
// Custom headersresp, _=c.Get(ctx, "https://httpbin.org/headers", map[string][]string{
"X-Custom": {"value"},
})importhttpcloakfrom"httpcloak";// Simple requestconstsession=newhttpcloak.Session({preset: "chrome-latest"});constr=awaitsession.get("https://example.com");console.log(r.statusCode,r.protocol);// POST with JSONconstr=awaitsession.post("https://httpbin.org/post",{json: {key: "value"}});// Custom headersconstr=awaitsession.get("https://httpbin.org/headers",{headers: {"X-Custom": "value"}});session.close();usingHttpCloak;// Simple requestusingvarsession=newSession(preset:Presets.Chrome145);varr=session.Get("https://example.com");Console.WriteLine($"{r.StatusCode}{r.Protocol}");// POST with JSONvarr=session.PostJson("https://httpbin.org/post",new{key="value"});// Custom headersvarr=session.Get("https://httpbin.org/headers",headers:newDictionary<string,string>{["X-Custom"]="value"});Don't have a preset for your target browser? Capture once, use forever. Visit tls.peet.ws/api/all in the browser you want to mimic, paste the JA3 + Akamai fingerprint into a JSON spec, register it, and you have a brand-new preset that emits real wire bytes.
importjson, httpcloak# 1. Capture: visit tls.peet.ws/api/all in the browser, copy two fields.PEET_JA3="771,4865-4866-4867-49195-49199-49196-49200-...,29-23-24,0"PEET_AKAMAI="1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p"# 2. Start from any built-in preset, swap in the captured fingerprint.spec=json.loads(httpcloak.describe_preset("chrome-latest"))
spec["preset"]["name"] ="my-browser"spec["preset"]["tls"] = {"ja3": PEET_JA3}
spec["preset"]["http2"]["akamai"] =PEET_AKAMAI# 3. Register, use like any built-in preset.httpcloak.load_preset_from_json(json.dumps(spec))
session=httpcloak.Session(preset="my-browser")
r=session.get("https://target.com/")describe_preset emits every effective field — TLS extensions, HTTP/2 SETTINGS order, HPACK encoding order, per-resource-type stream priority table, QUIC transport params, TCP/IP fingerprint, full header set — so anything you see in the JSON is editable. Mutated specs round-trip byte-equal through load_preset_from_json → run → describe_preset: same wire mechanics, just the values you changed.
Same workflow across all bindings:
| Describe | Load | Unregister | |
|---|---|---|---|
| Python | httpcloak.describe_preset(name) | httpcloak.load_preset_from_json(json) | httpcloak.unregister_preset(name) |
| Node.js | describePreset(name) | loadPresetFromJSON(json) | unregisterPreset(name) |
| .NET | CustomPresets.Describe(name) | CustomPresets.LoadFromJson(json) | CustomPresets.Unregister(name) |
| Go | fingerprint.Describe(name) | fingerprint.LoadPresetFromJSON(json) | fingerprint.Unregister(name) |
Pool dozens of fingerprints with PresetPool (round-robin / random rotation, all bindings). Drill-down recipes — bumping a single H2 priority, inserting an HPACK header, importing a peet.ws capture, cleaning up — in examples/python-examples/17_tweak_fingerprint.py, examples/js-examples/18_tweak_fingerprint.js, and examples/csharp-examples/TweakFingerprint.cs.
Hides which domain you're connecting to from network observers.
session=httpcloak.Session(
preset="chrome-latest",
ech_config_domain="cloudflare-ech.com"# Fetches ECH config from DNS
)Cloudflare trace shows sni=encrypted instead of sni=plaintext. Use cloudflare-ech.com (the dedicated ECH domain) for any Cloudflare-fronted target.
TLS session tickets make you look like a returning visitor.
# Warm up on any Cloudflare sitesession.get("https://cloudflare.com/")
session.save("session.json")
# Use on your targetsession=httpcloak.Session.load("session.json")
r=session.get("https://target.com/") # Bot score: 99Cross-domain warming works because Cloudflare sites share TLS infrastructure.
Two methods for QUIC through proxies:
| Method | How it works |
|---|---|
| SOCKS5 UDP ASSOCIATE | Proxy relays UDP packets. Most residential proxies support this. |
| MASQUE (CONNECT-UDP) | RFC 9298. Tunnels UDP over HTTP/3. Premium providers only. |
# SOCKS5 with UDPsession=httpcloak.Session(proxy="socks5://user:pass@proxy:1080")
# MASQUEsession=httpcloak.Session(proxy="masque://proxy:443")Known MASQUE providers (auto-detected): Bright Data, Oxylabs, Smartproxy, SOAX.
Speculative TLS (opt-in): CONNECT + TLS ClientHello are sent together, saving one proxy round-trip (~25% faster). Enable for compatible proxies:
session=httpcloak.Session(proxy="socks5://...", enable_speculative_tls=True)Connect to a different host than what appears in TLS SNI.
client:=httpcloak.NewClient("chrome-latest",
httpcloak.WithConnectTo("public-cdn.com", "actual-backend.internal"),
)client.PinCertificate("sha256/AAAA...",
httpcloak.PinOptions{IncludeSubdomains: true})client.OnPreRequest(func(req*http.Request) error {
req.Header.Set("X-Custom", "value")
returnnil
})
client.OnPostResponse(func(resp*httpcloak.Response) {
log.Printf("Got %d from %s", resp.StatusCode, resp.FinalURL)
})fmt.Printf("DNS: %dms, TCP: %dms, TLS: %dms, Total: %dms\n",
resp.Timing.DNSLookup,
resp.Timing.TCPConnect,
resp.Timing.TLSHandshake,
resp.Timing.Total)session=httpcloak.Session(preset="chrome-latest", http_version="h3") # Force HTTP/3session=httpcloak.Session(preset="chrome-latest", http_version="h2") # Force HTTP/2session=httpcloak.Session(preset="chrome-latest", http_version="h1") # Force HTTP/1.1Auto mode tries HTTP/3 first, falls back gracefully.
Anti-bot systems inspect TCP SYN packet parameters (TTL, Window Size, MSS, Window Scale) to verify your claimed OS matches. A request claiming Chrome on Windows but with Linux TCP parameters (TTL=64) is instantly flagged.
httpcloak automatically sets the correct TCP/IP fingerprint for each preset's platform. You can also override manually:
session=httpcloak.Session(
preset="chrome-latest-windows",
tcp_ttl=128, # Windows=128, Linux/macOS=64tcp_window_size=64240, # Windows=64240, Linux/macOS=65535tcp_window_scale=8, # Windows=8, Linux=7, macOS=6tcp_mss=1460, # Standard Ethernet MTU
)Go:
client:=client.NewClient("chrome-latest-windows",
client.WithTCPFingerprint(fingerprint.TCPFingerprint{
TTL: 128, MSS: 1460, WindowSize: 64240, WindowScale: 8, DFBit: true,
}),
)Node.js:
constsession=newhttpcloak.Session({preset: "chrome-latest-windows",tcpTtl: 128,tcpWindowSize: 64240,tcpWindowScale: 8,});C#:
varsession=newSession(preset:Presets.Chrome145Windows,tcpTtl:128,tcpWindowSize:64240,tcpWindowScale:8);Built-in platform profiles: Windows (TTL=128, WS=8), Linux (TTL=64, WS=7), macOS (TTL=64, WS=6).
Switch proxies mid-session without creating new connections. Perfect for proxy rotation.
session=httpcloak.Session(preset="chrome-latest")
# Start with direct connectionr=session.get("https://api.ipify.org")
print(f"Direct IP: {r.text}")
# Switch to proxy 1session.set_proxy("http://proxy1.example.com:8080")
r=session.get("https://api.ipify.org")
print(f"Proxy 1 IP: {r.text}")
# Switch to proxy 2session.set_proxy("socks5://proxy2.example.com:1080")
r=session.get("https://api.ipify.org")
print(f"Proxy 2 IP: {r.text}")
# Back to directsession.set_proxy("")Split proxy configuration - use different proxies for HTTP/2 and HTTP/3:
session=httpcloak.Session(preset="chrome-latest")
# TCP proxy for HTTP/1.1 and HTTP/2session.set_tcp_proxy("http://tcp-proxy.example.com:8080")
# UDP proxy for HTTP/3 (requires SOCKS5 UDP ASSOCIATE or MASQUE)session.set_udp_proxy("socks5://udp-proxy.example.com:1080")
# Check current configurationprint(session.get_tcp_proxy()) # TCP proxy URLprint(session.get_udp_proxy()) # UDP proxy URLControl the order headers are sent for advanced fingerprinting scenarios.
session=httpcloak.Session(preset="chrome-latest")
# Get the current header order (from preset)print(session.get_header_order())
# Set custom header ordersession.set_header_order([
"accept-language", "sec-ch-ua", "accept",
"sec-fetch-site", "sec-fetch-mode", "user-agent",
"sec-ch-ua-platform", "sec-ch-ua-mobile"
])
# Make request with custom orderr=session.get("https://example.com")
# Reset to preset's default ordersession.set_header_order([])JavaScript:
session.setHeaderOrder(["accept-language","sec-ch-ua","accept", ...]);console.log(session.getHeaderOrder());session.setHeaderOrder([]);// Reset to defaultC#:
session.SetHeaderOrder(new[]{"accept-language","sec-ch-ua","accept", ...});Console.WriteLine(string.Join(", ",session.GetHeaderOrder()));session.SetHeaderOrder(null);// Reset to defaultGo:
c.SetHeaderOrder([]string{"accept-language", "sec-ch-ua", "accept", ...})
fmt.Println(c.GetHeaderOrder())
c.SetHeaderOrder(nil) // Reset to default# Stream large downloadsstream=session.get_stream("https://example.com/large-file.zip")
print(f"Size: {stream.content_length} bytes")
withopen("file.zip", "wb") asf:
whileTrue:
chunk=stream.read(8192)
ifnotchunk:
breakf.write(chunk)
stream.close()
# Iterator patternforchunkinsession.get_stream(url).iter_content(chunk_size=8192):
process(chunk)
# Multipart uploadr=session.post(url, files={
"file": ("filename.jpg", file_bytes, "image/jpeg")
})# Basic authr=httpcloak.get("https://api.example.com/data",
auth=("username", "password"),
preset="chrome-latest"
)
# Session-level authsession=httpcloak.Session(
preset="chrome-latest",
auth=("username", "password")
)# Timeoutsession=httpcloak.Session(preset="chrome-latest", timeout=30)
# Per-request timeoutr=session.get("https://slow-api.com/data", timeout=60)// Go: Timeout and retry configurationclient:=client.NewClient("chrome-latest",
client.WithTimeout(30*time.Second),
client.WithRetry(3), // Retry 3 times on 429, 500, 502, 503, 504client.WithRetryConfig(
5, // Max retries500*time.Millisecond, // Min backoff10*time.Second, // Max backoff
[]int{429, 503}, // Status codes to retry
),
)// Disable automatic redirectsclient:=client.NewClient("chrome-latest",
client.WithoutRedirects(),
)
resp, _:=client.Get(ctx, "https://example.com/redirect", nil)
fmt.Println(resp.StatusCode) // 302fmt.Println(resp.GetHeader("location")) // Redirect URLSimulates a browser page refresh - closes all TCP/QUIC connections but keeps TLS session cache intact. On next request, connections use TLS resumption (like a real browser).
session=httpcloak.Session(preset="chrome-latest")
# Make some requestssession.get("https://example.com/page1")
session.get("https://example.com/page2")
# Simulate browser refresh (F5)session.refresh()
# Next request uses TLS resumption, looks like returning visitorsession.get("https://example.com/page1")Go:
session:=httpcloak.NewSession("chrome-latest")
session.Get(ctx, "https://example.com")
session.Refresh() // Close connections, keep TLS cachesession.Get(ctx, "https://example.com") // TLS resumptionNode.js:
session.refresh();C#:
session.Refresh();Simulates a real browser page load - fetches the HTML page and all its subresources (CSS, JS, images, fonts) with realistic headers, priorities, and timing. After warmup, the session has TLS session tickets, cookies, and cache headers populated.
session=httpcloak.Session(preset="chrome-latest")
# Fetches page + subresources with realistic browser behaviorsession.warmup("https://example.com")
# Subsequent requests look like follow-up navigation from a real userr=session.get("https://example.com/api/data")Go:
session:=httpcloak.NewSession("chrome-latest")
session.Warmup(ctx, "https://example.com")
session.Get(ctx, "https://example.com/api/data") // Looks like real userNode.js:
session.warmup("https://example.com");C#:
session.Warmup("https://example.com");Creates N sessions that share cookies and TLS session caches with the parent but have independent connections. This simulates multiple browser tabs - same cookies, same TLS resumption tickets, same fingerprint, but independent TCP/QUIC connections for parallel requests.
session=httpcloak.Session(preset="chrome-latest")
session.warmup("https://example.com")
# Create 10 parallel "tabs" sharing cookies + TLS cachetabs=session.fork(10)
fori, tabinenumerate(tabs):
threading.Thread(
target=lambdat, n: t.get(f"https://example.com/page/{n}"),
args=(tab, i)
).start()Go:
session:=httpcloak.NewSession("chrome-latest")
session.Warmup(ctx, "https://example.com")
tabs:=session.Fork(10)
fori, tab:=rangetabs {
gofunc(t*httpcloak.Session, nint) {
t.Get(ctx, fmt.Sprintf("https://example.com/page/%d", n))
}(tab, i)
}Node.js:
session.warmup("https://example.com");consttabs=session.fork(10);awaitPromise.all(tabs.map((tab,i)=>tab.get(`https://example.com/page/${i}`)));C#:
session.Warmup("https://example.com");vartabs=session.Fork(10);awaitTask.WhenAll(tabs.Select((tab,i)=>Task.Run(()=>tab.Get($"https://example.com/page/{i}"))));Bind outgoing connections to a specific local IP address. Essential for IPv6 rotation scenarios where you have multiple IPs assigned to your machine.
# Bind to specific IPv6 addresssession=httpcloak.Session(
preset="chrome-latest",
local_address="2001:db8::1"
)
# All requests use this source IPr=session.get("https://api.ipify.org")
print(r.text) # Shows 2001:db8::1# IPv4 works toosession=httpcloak.Session(
preset="chrome-latest",
local_address="192.168.1.100"
)Go:
session:=httpcloak.NewSession("chrome-latest",
httpcloak.WithLocalAddress("2001:db8::1"),
)Node.js:
constsession=newhttpcloak.Session({preset: "chrome-latest",localAddress: "2001:db8::1"});C#:
varsession=newSession(preset:Presets.Chrome145,localAddress:"2001:db8::1");Note: When a local address is set, target IPs are automatically filtered to match the address family (IPv6 local → only IPv6 targets).
Write TLS session keys to a file for traffic decryption in Wireshark. Works with HTTP/1.1, HTTP/2, and HTTP/3.
session=httpcloak.Session(
preset="chrome-latest",
key_log_file="/tmp/keys.log"
)
# Make requests - keys written to filesession.get("https://example.com")
# In Wireshark: Edit → Preferences → Protocols → TLS → (Pre)-Master-Secret log filenameGo:
session:=httpcloak.NewSession("chrome-latest",
httpcloak.WithKeyLogFile("/tmp/keys.log"),
)Node.js:
constsession=newhttpcloak.Session({preset: "chrome-latest",keyLogFile: "/tmp/keys.log"});C#:
varsession=newSession(preset:Presets.Chrome145,keyLogFile:"/tmp/keys.log");Also supports SSLKEYLOGFILE environment variable (standard NSS Key Log Format).
importhttpcloak# Module-level functionshttpcloak.get(url, **kwargs)
httpcloak.post(url, **kwargs)
httpcloak.put(url, **kwargs)
httpcloak.patch(url, **kwargs)
httpcloak.delete(url, **kwargs)
httpcloak.head(url, **kwargs)
httpcloak.options(url, **kwargs)
# Session classsession=httpcloak.Session(
preset="chrome-latest", # Browser preset (default)proxy="socks5://...", # Proxy URLtimeout=30, # Timeout in secondshttp_version="h3", # Force protocol: h1, h2, h3, autoech_config_domain="cloudflare-ech.com", # ECH config source domainauth=("user", "pass"), # Basic auth
)
# Session methodssession.get(url, **kwargs)
session.post(url, data=None, json=None, **kwargs)
session.get_stream(url) # Streaming downloadsession.close()
# Proxy switchingsession.set_proxy(url) # Set both TCP and UDP proxysession.set_tcp_proxy(url) # Set TCP proxy only (H1/H2)session.set_udp_proxy(url) # Set UDP proxy only (H3)session.get_proxy() # Get current proxysession.get_tcp_proxy() # Get current TCP proxysession.get_udp_proxy() # Get current UDP proxy# Header order customizationsession.set_header_order(order) # Set custom header order (list of lowercase names)session.get_header_order() # Get current header order# Session persistence (0-RTT resumption)session.save("session.json") # Save to filesession=Session.load("session.json") # Load from filedata=session.marshal() # Export as stringsession=Session.unmarshal(data) # Import from string# Response objectresponse.status_code# HTTP statusresponse.ok# True if status < 400response.text# Body as stringresponse.content# Body as bytesresponse.json() # Parse JSONresponse.headers# Response headersresponse.protocol# h1, h2, or h3response.url# Final URLresponse.raise_for_status() # Raise on 4xx/5xximport"github.com/sardanioss/httpcloak/client"// Client creationc:=client.NewClient("chrome-latest",
client.WithTimeout(30*time.Second),
client.WithProxy("socks5://..."),
client.WithRetry(3),
client.WithoutRedirects(),
client.WithInsecureSkipVerify(),
)
deferc.Close()
// Request methodsresp, err:=c.Get(ctx, url, headers)
resp, err:=c.Post(ctx, url, body, headers)
resp, err:=c.Put(ctx, url, body, headers)
resp, err:=c.Delete(ctx, url, headers)
// Advanced requestresp, err:=c.Do(ctx, &client.Request{
Method: "GET",
URL: url,
Headers: map[string][]string{},
Body: io.Reader,
Params: map[string]string{},
ForceProtocol: client.ProtocolHTTP3,
FetchMode: client.FetchModeCORS,
Referer: "https://example.com",
})
// Proxy switchingc.SetProxy(url) // Set both TCP and UDP proxyc.SetTCPProxy(url) // Set TCP proxy only (H1/H2)c.SetUDPProxy(url) // Set UDP proxy only (H3)c.GetProxy() // Get current proxyc.GetTCPProxy() // Get current TCP proxyc.GetUDPProxy() // Get current UDP proxy// Session persistence (0-RTT resumption)c.Save("session.json") // Save to filec, _=client.Load("session.json") // Load from filedata, _:=c.Marshal() // Export as stringc, _=client.Unmarshal(data) // Import from string// Response objectresp.StatusCoderesp.Protocolresp.Headersresp.Body// io.ReadCloserresp.Text() // (string, error)resp.Bytes() // ([]byte, error)resp.JSON(&v) // errorresp.GetHeader(key) // stringresp.IsSuccess() // boolresp.IsRedirect() // boolimporthttpcloakfrom"httpcloak";// Session creationconstsession=newhttpcloak.Session({preset: "chrome-latest",proxy: "socks5://...",timeout: 30000,httpVersion: "h3",});// Async methodsawaitsession.get(url,options)awaitsession.post(url,{ json, data, headers })awaitsession.put(url,options)awaitsession.delete(url,options)// Sync methodssession.getSync(url,options)session.postSync(url,options)session.close()// Proxy switchingsession.setProxy(url)// Set both TCP and UDP proxysession.setTcpProxy(url)// Set TCP proxy only (H1/H2)session.setUdpProxy(url)// Set UDP proxy only (H3)session.getProxy()// Get current proxysession.getTcpProxy()// Get current TCP proxysession.getUdpProxy()// Get current UDP proxysession.proxy// Property accessor (get/set)// Session persistence (0-RTT resumption)session.save("session.json")// Save to filesession=httpcloak.Session.load("session.json")// Load from fileconstdata=session.marshal()// Export as stringsession=httpcloak.Session.unmarshal(data)// Import from string// Response objectresponse.statusCoderesponse.okresponse.textresponse.json()response.headersresponse.protocolusingHttpCloak;// Session creationvarsession=newSession(preset:Presets.Chrome145,proxy:"socks5://...",timeout:30);// Request methodssession.Get(url,headers)
session.Post(url,body,headers)
session.PostJson<T>(url,data,headers)session.Put(url,body,headers)
session.Delete(url)
session.Dispose()// Proxy switching
session.SetProxy(url)// Set both TCP and UDP proxysession.SetTcpProxy(url)// Set TCP proxy only (H1/H2)
session.SetUdpProxy(url)// Set UDP proxy only (H3)
session.GetProxy()// Get current proxy
session.GetTcpProxy()// Get current TCP proxy
session.GetUdpProxy()// Get current UDP proxysession.Proxy// Property accessor (get/set)// Session persistence (0-RTT resumption)session.Save("session.json")// Save to filevarsession=Session.Load("session.json")// Load from filevardata=session.Marshal()// Export as string
var session = Session.Unmarshal(data)// Import from string// Response objectresponse.StatusCoderesponse.Ok
response.Text
response.Json<T>()response.Headersresponse.Protocol| Preset | Platform | PQ | H3 |
|---|---|---|---|
chrome-146 | Auto | ✅ | ✅ |
chrome-146-windows | Windows | ✅ | ✅ |
chrome-146-macos | macOS | ✅ | ✅ |
chrome-146-linux | Linux | ✅ | ✅ |
chrome-146-ios | iOS | ✅ | ✅ |
chrome-146-android | Android | ✅ | ✅ |
chrome-145 | Auto | ✅ | ✅ |
chrome-145-windows | Windows | ✅ | ✅ |
chrome-145-macos | macOS | ✅ | ✅ |
chrome-145-linux | Linux | ✅ | ✅ |
chrome-145-ios | iOS | ✅ | ✅ |
chrome-145-android | Android | ✅ | ✅ |
chrome-144 | Auto | ✅ | ✅ |
chrome-144-windows | Windows | ✅ | ✅ |
chrome-144-macos | macOS | ✅ | ✅ |
chrome-144-linux | Linux | ✅ | ✅ |
chrome-143 | Auto | ✅ | ✅ |
chrome-143-windows | Windows | ✅ | ✅ |
chrome-143-macos | macOS | ✅ | ✅ |
chrome-143-linux | Linux | ✅ | ✅ |
chrome-141 | Auto | ✅ | ❌ |
chrome-133 | Auto | ✅ | ❌ |
firefox-133 | Auto | ❌ | ❌ |
safari-18 | macOS | ❌ | ✅ |
safari-18-ios | iOS | ❌ | ✅ |
safari-17-ios | iOS | ❌ | ❌ |
chrome-146-ios | iOS | ✅ | ✅ |
chrome-145-ios | iOS | ✅ | ✅ |
chrome-144-ios | iOS | ✅ | ✅ |
chrome-143-ios | iOS | ✅ | ✅ |
chrome-146-android | Android | ✅ | ✅ |
chrome-145-android | Android | ✅ | ✅ |
chrome-144-android | Android | ✅ | ✅ |
chrome-143-android | Android | ✅ | ✅ |
PQ = Post-Quantum (X25519MLKEM768) · H3 = HTTP/3
Any field below is editable in the JSON spec produced by describe_preset (see the Build Any Browser Fingerprint From JSON section above for the workflow):
| Path | What it controls |
|---|---|
preset.tls.ja3 | JA3 string (cipher suites, extensions, curves) |
preset.http2.akamai | H2 SETTINGS / WINDOW_UPDATE / PRIORITY / pseudo-order shorthand |
preset.http2.priority_table[dest] | Per-resource-type H2 stream priority (sec-fetch-dest → urgency) |
preset.http2.hpack_header_order | HPACK encoding order |
preset.http2.settings_order | SETTINGS frame ID order |
preset.http2.pseudo_order | HTTP/2 pseudo-header order |
preset.http3 | HTTP/3 / QUIC parameters |
preset.tcp | TCP/IP fingerprint |
preset.headers.values / preset.headers.order | Header values and request order |
Round-trip is byte-equal — describe_preset → mutate JSON → load_preset_from_json → run → describe_preset returns identical bytes.
| Tool | Tests |
|---|---|
| tls.peet.ws | JA3, JA4, HTTP/2 Akamai |
| quic.browserleaks.com | HTTP/3 QUIC fingerprint |
| cf.erisa.uk | Cloudflare bot score |
| cloudflare.com/cdn-cgi/trace | ECH status, TLS version |
Custom forks for browser-accurate fingerprinting:
- sardanioss/utls — TLS fingerprinting
- sardanioss/quic-go — HTTP/3 fingerprinting
- sardanioss/net — HTTP/2 frame fingerprinting
Full docs at httpcloak.dev. Some entry points:
- Getting Started — first request, presets, common options.
- Fingerprinting — JA3 / JA4 / Akamai shorthand, JSON preset builder, per-resource priority.
- Proxies — HTTP CONNECT, SOCKS5, SOCKS5 UDP, MASQUE, source-address binding.
- Connection Lifecycle —
Refresh,Warmup,Fork, save / restore. - Recipes — multi-proxy rotation, custom Chrome from tls.peet, long-running scrapers, Wireshark debugging, Local Proxy server.
- Reference — every option, every preset, the JSON spec, architecture map.
- Bindings — Go / Python / Node.js / .NET specifics.
LLM-friendly indexes for AI agents: llms.txt and llms-full.txt.
- Discord: sardanioss
- Email: sakshamsolanki126@gmail.com
MIT License
