First Request
httpcloak puts the same bytes on the wire as a real browser. Same TLS ClientHello, same HTTP/2 SETTINGS frame, same header order, same priority frames. If a site fingerprints your client, you show up looking like Chrome (or Firefox, or Safari) instead of Go's net/http or Python requests.
This page is the four-line "does it work" check. Pick your language, copy the snippet, run it. You should get a 200 back from tls.peet.ws/api/all with a Chrome-shaped fingerprint in the response.
The snippet
- Go
- Python
- Node.js
- .NET
package main
import (
"context"
"fmt"
"time"
"github.com/sardanioss/httpcloak"
)
func main() {
sess := httpcloak.NewSession("chrome-latest",
httpcloak.WithSessionTimeout(30*time.Second),
)
defer sess.Close()
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
resp, err := sess.Get(ctx, "https://tls.peet.ws/api/all")
if err != nil {
panic(err)
}
defer resp.Close()
fmt.Println("status:", resp.StatusCode)
fmt.Println("protocol:", resp.Protocol)
body, _ := resp.Text()
fmt.Println(body)
}
import httpcloak
with httpcloak.Session(preset="chrome-latest", timeout=30) as session:
r = session.get("https://tls.peet.ws/api/all")
print("status:", r.status_code)
print("protocol:", r.http_version)
print(r.text)
const { Session } = require("httpcloak");
(async () => {
const session = new Session({ preset: "chrome-latest", timeout: 30 });
try {
const r = await session.get("https://tls.peet.ws/api/all");
console.log("status:", r.statusCode);
console.log("protocol:", r.httpVersion);
console.log(r.text);
} finally {
session.close();
}
})();
using HttpCloak;
using var session = new Session(preset: "chrome-latest", timeout: 30);
var r = session.Get("https://tls.peet.ws/api/all");
Console.WriteLine($"status: {r.StatusCode}");
Console.WriteLine($"protocol: {r.HttpVersion}");
Console.WriteLine(r.Text);
What you should see
The full response is a chunky JSON blob with TLS, HTTP/2, and header data. Here's the trimmed version with the parts that actually matter:
{
"http_version": "h2",
"tls": {
"ja3_hash": "55ecc08008f90a8b2a5c5289ab0f8b69",
"ja4": "t13d1516h2_8daaf6152771_d8a2da3f94cd"
},
"http2": {
"akamai_fingerprint": "1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p",
"akamai_fingerprint_hash": "52d84b11737d980aef856699f885ca86"
},
"user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36"
}
A few things worth flagging:
http_versionish2. Chrome speaks HTTP/2 by default to anything ALPN-capable, so you do too. HTTP/3 kicks in if the server advertises it via Alt-Svc. Pin one withWithForceHTTP2()orWithForceHTTP3()if you'd rather not let it negotiate.ja4is stable across runs on the same preset.ja3_hashisn't, because Chrome shuffles GREASE extension values on every ClientHello and that bleeds into the JA3 string. JA4 strips GREASE. Match against JA4, ignore JA3.akamai_fingerprint_hashrolls up H2 SETTINGS, WINDOW_UPDATE, PRIORITY, and pseudo-header order into one value. It should line up with what real Chrome 148 ships.
Bookmark tls.peet.ws/api/all. Anytime you tweak a preset, drop in a custom JA3, or wonder why a target's still flagging you, hit this endpoint and diff the response against a real browser. DevTools won't even show you the request header order, so this is the easiest source of truth.
Where to next
- Presets Explained for what
chrome-latestactually bundles and how to pick something else. - Common Options for timeouts, retries, redirects, and the boring stuff every client has.
- Fingerprinting overview when you want to start hand-tuning the wire bytes.