Skip to main content

Presets

A preset is the whole fingerprint bundle for one browser version on one platform. It packs:

  • TLS ClientHello (cipher list, extension list, supported groups, signature algorithms, ALPN, cert compression).
  • HTTP/2 SETTINGS values, WINDOW_UPDATE, pseudo-header order.
  • Default HTTP headers in the exact order Chrome / Firefox / Safari ships them.
  • RFC 7540 stream priorities and the RFC 9218 priority table per Sec-Fetch-Dest.
  • HTTP/3 / QUIC transport parameters (only on presets that support h3).
  • TCP/IP fingerprint hints (TTL, MSS, window size, for OS-level matching).

Pick one by name, send a request, done. The wire bytes match the real browser.

Picking the right preset

  • Default to chrome-latest. Works against the widest range of targets. Auto-tracks the newest Chrome we've shipped.
  • Reach for android-chrome-latest if you need a mobile UA. Mobile traffic gets scored differently on most anti-bot stacks. TLS handshake's identical to desktop Chrome, but the User-Agent and sec-ch-ua-mobile: ?1 flag the mobile path.
  • Use ios-safari-18 (or safari-18-ios) if you need an iPhone fingerprint. Different cipher list, different pseudo-header order, no RFC 7540 priorities, smaller QUIC stream window. Targets that profile iOS users will spot a Chrome preset pretending to be an iPhone in seconds.
  • Pick firefox-148 if the target only accepts Firefox. Different cipher list, different SETTINGS layout (smaller initial window, smaller max frame size), different pseudo-header order (m,p,a,s vs Chrome's m,a,s,p).

Available preset families

Chrome

Versions 133, 141, 143, 144, 145, 146, 147, 148. Each version has per-OS variants:

FamilyVariants
Desktopchrome-148, chrome-148-windows, chrome-148-linux, chrome-148-macos
Androidchrome-148-android (alias: android-chrome-148)
iOSchrome-148-ios (alias: ios-chrome-148)

Bare chrome-148 resolves to the host OS at runtime via runtime.GOOS. So on a Linux box, chrome-148 gives you chrome-148-linux. Want the same platform UA no matter where the code runs? Use the explicit variant.

Chrome -latest aliases

Aliases that auto-track the newest shipped Chrome:

chrome-latest → chrome-148
chrome-latest-windows → chrome-148-windows
chrome-latest-linux → chrome-148-linux
chrome-latest-macos → chrome-148-macos
chrome-latest-android → chrome-148-android
chrome-latest-ios → chrome-148-ios

When Chrome 149 ships, those aliases bump in lockstep. Code on chrome-latest keeps rolling. Code that pinned chrome-148-windows stays on the same fingerprint.

Firefox

firefox-133, firefox-148, firefox-latest. No per-OS variants, Firefox doesn't bake enough OS info into its fingerprint for that to matter. No h3 yet either, Firefox has its own h3 quirks we haven't built out.

Safari

PresetNotes
safari-18 (safari-latest)Desktop macOS Safari 18, supports h3
safari-17-ios (ios-safari-17)iPhone Safari 17, h2 only
safari-18-ios (ios-safari-18, safari-latest-ios)iPhone Safari 18, supports h3

Safari sets NoRFC7540Priorities=true, so it never emits the H2 PRIORITY frame. RFC 9218 priority headers carry the signal instead. That's the single biggest tell that splits a Safari fingerprint from a Chrome one at the H2 layer, even though both ALPN as h2.

Backwards-compat aliases

The older <os>-<browser>-<version> naming still works for folks on older docs:

ios-chrome-148 → chrome-148-ios
ios-safari-18 → safari-18-ios
android-chrome-148 → chrome-148-android

Both forms resolve to the same preset.

Inheritance: how a new Chrome version ships in 30 seconds

Each Chrome minor bump is usually pure UA + sec-ch-ua delta. TLS fingerprint, H2 SETTINGS, header order, priority table, all the same as the version before. So Chrome 148 isn't a from-scratch Go file. It's a JSON delta over Chrome 147:

{
"version": 1,
"preset": {
"name": "chrome-148-windows",
"based_on": "chrome-147-windows",
"headers": {
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Safari/537.36",
"values": {
"sec-ch-ua": "\"Chromium\";v=\"148\", \"Google Chrome\";v=\"148\", \"Not/A)Brand\";v=\"99\""
},
"order": [
{"key": "sec-ch-ua", "value": "\"Chromium\";v=\"148\", \"Google Chrome\";v=\"148\", \"Not/A)Brand\";v=\"99\""},
{"key": "sec-ch-ua-mobile", "value": "?0"},
...
]
}
}
}

That's the whole patch. TLS bytes come from chrome-147-windows (which itself inherits TLS bytes from chrome-146-windows because nothing changed in 147). H2 SETTINGS, priority table, everything else, all inherited.

You can do the same. Pick a preset, dump it, change three fields, register the result. See JSON Preset Builder.

Verification

Hit tls.peet.ws/api/all with each preset and you'll see the matching JA4 / Akamai hash:

package main

import (
"context"
"fmt"
"io"

"github.com/sardanioss/httpcloak"
)

func main() {
for _, name := range []string{"chrome-latest", "android-chrome-148", "firefox-148", "safari-18-ios"} {
s := httpcloak.NewSession(name)
resp, _ := s.Get(context.Background(), "https://tls.peet.ws/api/all")
body, _ := io.ReadAll(resp.Body)
resp.Body.Close()
s.Close()
fmt.Println(name, string(body))
}
}

Captured fingerprints (run on 2026-05, against tls.peet.ws/api/all):

chrome-latest ja4=t13d1516h2_8daaf6152771_d8a2da3f94cd peetprint_hash=1d4ffe9b0e34acac0bd883fa7f79d7b5 akamai_fingerprint_hash=52d84b11737d980aef856699f885ca86
chrome-148-windows ja4=t13d1516h2_8daaf6152771_d8a2da3f94cd peetprint_hash=1d4ffe9b0e34acac0bd883fa7f79d7b5 akamai_fingerprint_hash=52d84b11737d980aef856699f885ca86
chrome-148-linux ja4=t13d1516h2_8daaf6152771_d8a2da3f94cd peetprint_hash=1d4ffe9b0e34acac0bd883fa7f79d7b5 akamai_fingerprint_hash=52d84b11737d980aef856699f885ca86
chrome-148-macos ja4=t13d1516h2_8daaf6152771_d8a2da3f94cd peetprint_hash=1d4ffe9b0e34acac0bd883fa7f79d7b5 akamai_fingerprint_hash=52d84b11737d980aef856699f885ca86
android-chrome-148 ja4=t13d1516h2_8daaf6152771_d8a2da3f94cd peetprint_hash=1d4ffe9b0e34acac0bd883fa7f79d7b5 akamai_fingerprint_hash=52d84b11737d980aef856699f885ca86
firefox-148 ja4=t13d1717h2_5b57614c22b0_3cbfd9057e0d peetprint_hash=89d89662b21018947a9a46658c4f5ede akamai_fingerprint_hash=6ea73faa8fc5aac76bded7bd238f6433
safari-18 ja4=t13d2013h2_a09f3c656075_7f0f34a4126d peetprint_hash=62b834de729e78a9f0ebd1dd099314a7 akamai_fingerprint_hash=90d8353e47699c4c38ecd773e9b5a089
safari-18-ios ja4=t13d2013h2_a09f3c656075_7f0f34a4126d peetprint_hash=62b834de729e78a9f0ebd1dd099314a7 akamai_fingerprint_hash=90d8353e47699c4c38ecd773e9b5a089
chrome-148-ios ja4=t13d2013h2_a09f3c656075_7f0f34a4126d peetprint_hash=62b834de729e78a9f0ebd1dd099314a7 akamai_fingerprint_hash=c52879e43202aeb92740be6e8c86ea96

Things to spot:

  • Every Chrome desktop variant lands on the same JA4 / peetprint / akamai. The TLS handshake is genuinely identical across Windows / Linux / macOS Chrome. Only the User-Agent and sec-ch-ua-platform header tell you which OS you're on.
  • Android Chrome shares the same fingerprint as desktop Chrome too. Same TLS, same H2. The wire-level difference is the UA string (Mobile Safari/537.36) and sec-ch-ua-mobile: ?1.
  • Chrome on iOS shows up as Safari at the wire level, because iOS WebKit forces every browser onto the system networking stack. So chrome-148-ios shares its TLS handshake and JA4 hash with safari-18-ios. They split only on H2 SETTINGS values (chrome-148-ios advertises 2,3,4,9 vs Safari's 2,4,3,5,9) and the User-Agent.
  • Firefox and Safari each get their own JA4 / peetprint / akamai. Different cipher list, different SETTINGS, different pseudo-header order.
tip

The bare ja3_hash field won't be stable for Chrome presets across runs. Chrome shuffles its TLS extension order on every connection, so the raw JA3 string changes and the MD5 changes with it. JA4 sorts the extension list before hashing, that's why it's stable. Always verify against ja4 and peetprint_hash, never ja3_hash.

Full preset catalog

69 preset names total (counting -latest aliases and the old <os>-<browser> naming). For the exhaustive table with version numbers, supported protocols, and platform tags, see the Presets reference.