Skip to content

Repository files navigation

httpcloak

Go ReferencePyPInpmNuGet

Every Byte of your Request Indistinguishable from Chrome.

📖 Full documentation at httpcloak.dev



The Problem

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.

The Solution

importhttpcloakr=httpcloak.get("https://target.com", preset="chrome-latest")

That's it. Full browser transport layer fingerprint.


What Gets Emulated

🔐 TLS Layer

  • JA3 / JA4 fingerprints
  • GREASE randomization
  • Post-quantum X25519MLKEM768
  • ECH (Encrypted Client Hello)

🚀 Transport Layer

  • HTTP/2 SETTINGS frames
  • WINDOW_UPDATE values
  • Stream priorities (HPACK)
  • QUIC transport parameters
  • HTTP/3 GREASE frames
  • TCP/IP stack (TTL, MSS, Window)

🧠 Header Layer

  • Sec-Fetch-* coherence
  • Client Hints (Sec-Ch-UA)
  • Accept / Accept-Language
  • Header ordering
  • Cookie persistence

Results

┌─────────────────────────────────┐
│ ECH (Encrypted Client Hello) │
├─────────────────────────────────┤
│ WITHOUT: sni=plaintext │
│ WITH: sni=encrypted + │
└─────────────────────────────────┘
┌─────────────────────────────────┐
│ HTTP/3 Fingerprint Match │
├─────────────────────────────────┤
│ Protocol: h3 + │
│ QUIC Version: 1 + │
│ Transport Params: + │
│ GREASE Frames: + │
└─────────────────────────────────┘

vs curl_cffi

┌────────────────────────────────┬────────────────────────────────┐
│ 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# │
│ │ │
└────────────────────────────────┴────────────────────────────────┘

Install

pip install httpcloak # Python
npm install httpcloak # Node.js
go get github.com/sardanioss/httpcloak # Go
dotnet add package HttpCloak # C#

Quick Start

Python

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"
)

Go

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"},
})

Node.js

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();

C#

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"});

Features

🧬 Build Any Browser Fingerprint From JSON

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:

DescribeLoadUnregister
Pythonhttpcloak.describe_preset(name)httpcloak.load_preset_from_json(json)httpcloak.unregister_preset(name)
Node.jsdescribePreset(name)loadPresetFromJSON(json)unregisterPreset(name)
.NETCustomPresets.Describe(name)CustomPresets.LoadFromJson(json)CustomPresets.Unregister(name)
Gofingerprint.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.

🔐 ECH (Encrypted Client Hello)

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.

⚡ Session Resumption (0-RTT)

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: 99

Cross-domain warming works because Cloudflare sites share TLS infrastructure.

🌐 HTTP/3 Through Proxies

Two methods for QUIC through proxies:

MethodHow it works
SOCKS5 UDP ASSOCIATEProxy 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)

🎭 Domain Fronting

Connect to a different host than what appears in TLS SNI.

client:=httpcloak.NewClient("chrome-latest",
httpcloak.WithConnectTo("public-cdn.com", "actual-backend.internal"),
)

📌 Certificate Pinning

client.PinCertificate("sha256/AAAA...",
httpcloak.PinOptions{IncludeSubdomains: true})

🪝 Request Hooks

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)
})

⏱️ Request Timing

fmt.Printf("DNS: %dms, TCP: %dms, TLS: %dms, Total: %dms\n",
resp.Timing.DNSLookup,
resp.Timing.TCPConnect,
resp.Timing.TLSHandshake,
resp.Timing.Total)

🔄 Protocol Selection

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.1

Auto mode tries HTTP/3 first, falls back gracefully.

🖥️ TCP/IP Fingerprinting

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).

🔀 Runtime Proxy Switching

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 URL

📋 Header Order Customization

Control 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 default

C#:

session.SetHeaderOrder(new[]{"accept-language","sec-ch-ua","accept", ...});Console.WriteLine(string.Join(", ",session.GetHeaderOrder()));session.SetHeaderOrder(null);// Reset to default

Go:

c.SetHeaderOrder([]string{"accept-language", "sec-ch-ua", "accept", ...})
fmt.Println(c.GetHeaderOrder())
c.SetHeaderOrder(nil) // Reset to default

📤 Streaming & Uploads

# 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")
})

🔒 Authentication

# 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")
)

⏰ Timeouts & Retries

# 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
),
)

🚫 Redirect Control

// 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 URL

🔃 Refresh (Browser Page Refresh)

Simulates 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 resumption

Node.js:

session.refresh();

C#:

session.Refresh();

🌐 Warmup (Browser Page Load)

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 user

Node.js:

session.warmup("https://example.com");

C#:

session.Warmup("https://example.com");

🔀 Fork (Parallel Browser Tabs)

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}"))));

🌍 Local Address Binding

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).

🔑 TLS Key Logging

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 filename

Go:

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).


API Reference

Python

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/5xx

Go

import"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() // bool

Node.js

importhttpcloakfrom"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.protocol

C#

usingHttpCloak;// 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

Browser Presets

PresetPlatformPQH3
chrome-146Auto
chrome-146-windowsWindows
chrome-146-macosmacOS
chrome-146-linuxLinux
chrome-146-iosiOS
chrome-146-androidAndroid
chrome-145Auto
chrome-145-windowsWindows
chrome-145-macosmacOS
chrome-145-linuxLinux
chrome-145-iosiOS
chrome-145-androidAndroid
chrome-144Auto
chrome-144-windowsWindows
chrome-144-macosmacOS
chrome-144-linuxLinux
chrome-143Auto
chrome-143-windowsWindows
chrome-143-macosmacOS
chrome-143-linuxLinux
chrome-141Auto
chrome-133Auto
firefox-133Auto
safari-18macOS
safari-18-iosiOS
safari-17-iosiOS
chrome-146-iosiOS
chrome-145-iosiOS
chrome-144-iosiOS
chrome-143-iosiOS
chrome-146-androidAndroid
chrome-145-androidAndroid
chrome-144-androidAndroid
chrome-143-androidAndroid

PQ = Post-Quantum (X25519MLKEM768) · H3 = HTTP/3


Custom Preset Edit Points

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):

PathWhat it controls
preset.tls.ja3JA3 string (cipher suites, extensions, curves)
preset.http2.akamaiH2 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_orderHPACK encoding order
preset.http2.settings_orderSETTINGS frame ID order
preset.http2.pseudo_orderHTTP/2 pseudo-header order
preset.http3HTTP/3 / QUIC parameters
preset.tcpTCP/IP fingerprint
preset.headers.values / preset.headers.orderHeader values and request order

Round-trip is byte-equal — describe_preset → mutate JSON → load_preset_from_json → run → describe_preset returns identical bytes.


Testing Tools

ToolTests
tls.peet.wsJA3, JA4, HTTP/2 Akamai
quic.browserleaks.comHTTP/3 QUIC fingerprint
cf.erisa.ukCloudflare bot score
cloudflare.com/cdn-cgi/traceECH status, TLS version

Dependencies

Custom forks for browser-accurate fingerprinting:


Documentation

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 LifecycleRefresh, 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.


Connect


MIT License

About

Go HTTP client with browser-identical TLS/HTTP2 fingerprinting. Bypass bot detection by perfectly mimicking Chrome, Firefox, and Safari at the cryptographic level (JA3/JA4, Akamai fingerprint, header order). Supports HTTP/1.1, HTTP/2, HTTP/3, sessions, cookies, and proxies.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages