Comparing Browser Fingerprints of Popular Libraries and BlankTrail Proxy: TLS, HTTP/2 and HTTP/3

TLS, HTTP2/HTTP3, Chrome154, Edge154, Firefox156, Safari on Mac and iPhone, Android and TLS resumption: real library tests, supported profile configuration and BlankTrail with legacy software.

You can put Chrome/154.0.0.0, but the server sees the TLS handshake first, then HTTP/2 parameters, and only then the request headers. If those layers do not agree, the string “Chrome 154” does not turn an HTTP client into Chrome.

We compared the main public library families for impersonating browser network fingerprints with BlankTrail Proxy: ready-made profiles, attempts to impersonate Chrome 154, Firefox 156, Safari on macOS and iPhone, and Chrome on Android, as well as repeat connections, HTTP/2 and HTTP/3. We also asked a practical question: can you select the required version using the supported settings without rewriting the library, and how hard is it to integrate into your stack?

Research snapshot: October 4, 2026. Real requests from Windows, Linux and macOS, server-side measurements, TCP and QUIC ClientHello records, and captures from real browsers. For a quick public check, use BlankTrail /client, while the detailed analysis can be repeated using an independent public service. Detector summaries and raw-field comparisons are evaluated separately.

A shared methodology for comparing TLS, HTTP/2, QUIC/HTTP/3, browser profiles and headers.
A shared methodology for comparing TLS, HTTP/2, QUIC/HTTP/3, browser profiles and headers.

What the server actually compares

A TLS fingerprint comes from how a client builds its ClientHello: the cipher set and order, extensions and their contents, supported groups, signature algorithms, ALPN, GREASE, certificate compression, ALPS and key shares. For HTTP/2, the important details include SETTINGS and their order, WINDOW_UPDATE, pseudo-header order and prioritization parameters. At the HTTP layer, User-Agent, Client Hints and context-specific headers also matter.

JA4 is a useful summary, not a complete description of a connection. It is normal for several consecutive Chrome versions to share a JA4. In our reference captures, Chrome 152, 153 and 154 have the same fresh JA4 and basic HTTP/2 fingerprint. Those matches alone neither establish the exact version nor prove that extension contents are identical. For example, JA4 does not preserve the entire payload of trust_anchors , and excludes GREASE when calculating the relevant parts of the hash. The algorithm is documented in the JA4 repository.

How we compared the clients

The targets were Chrome 154, Edge 154, Firefox 156, Safari 26 and mobile profiles. We compared TLS, HTTP/2, raw HTTP/3 fields, headers and repeat connections against real browser captures. We tested both ready-made presets and supported manual configuration. Matching JA4, or receiving no warnings from /client, does not replace raw-field analysis.

Methodology, reference versions and measurement conditions

Libraries ran in separate test directories, while BlankTrail was controlled through its API. For the main configurations, we made four consecutive requests in one client session. The test endpoint closed the connection so that the next request could create a new TLS connection and use the ticket it had received. Another request on an already open HTTP/2 session was not counted as a TLS resumption test.

For each configuration, we saved the response from /client and the server-side record: TLS fields, PSK acceptance, HTTP/2 SETTINGS, connection window and pseudo-header order. We checked the brief detection result against the raw data.

Fresh Chrome 154 was measured using real Chrome 154.0.8037.93 on Windows with a separate headless profile. For the full ClientHello and resumption, we used saved captures from Chrome 154.0.8037.57 in the official Stable package, taken in headed mode under Xvfb. The package source and SHA256 were verified.

The TCP receiver saved ClientHello before completing the handshake. Its deliberate closure was not counted as a client error. We compared stable parameters and structure: random keys, random and session ID are expected to differ. For GREASE, we checked presence, position and shuffling, and for trust_anchors, the identifier set.

With HTTPX and primp, some repeat requests to the endpoint that closes connections failed while handling the closed HTTP/2 connection. We did not count those errors as fingerprint mismatches: those rows show only the network records actually collected, and incomplete runs are marked separately.

We tested 20 clients: browser profiles, raw TLS fields, HTTP/2 and HTTP/3, headers and repeat-connection behavior. The references were captures of real Chrome 154, Edge 154, Firefox 156, Safari 26.6.2 and mobile browsers. A matching JA4 alone did not count as a pass: we compared stable fields and protocol behavior, with the limits of confirmation stated alongside the results.

For the browser columns, we tested the available ready-made profiles and, where an API existed, manual parameters. We ran request series against a shared endpoint with the UAs of Chrome 154, Safari 26.6.2, Firefox 156, Edge 154 and Chrome Android 154, saving raw ClientHello and server-side TLS/H2 fields. In the Go series, idle connections were closed between requests while retaining the client and its cache. Windows curl was also tested with four URLs in one process. Built-in Node fetch was tested separately without replacing its dispatcher. Changing the UA alone does not count as configuring a transport profile.

Why a profile’s age matters

In the StatCounter sample, Chrome 154 accounted for more than half of versioned desktop Chrome traffic nine days after release, while Chrome 153 took thirteen days. An old preset quickly stops representing most traffic. The entire profile needs updating, not just the version number in User-Agent, although an old version alone does not prove automation. However, we observed that browser version matters greatly to most antifraud systems, and falling more than three major versions behind makes antibot checks harder to pass.

Chrome version adoption speed, calculated from daily StatCounter data. Source: gs.statcounter.com, CC BY-SA 3.0.
Chrome version adoption speed, calculated from daily StatCounter data. Source: gs.statcounter.com, CC BY-SA 3.0.
Data source and limitations

Calculated from daily data in StatCounter Desktop / Worldwide, September 8 to October 1, within recognized, versioned Chrome traffic. Android is excluded. These are page-view shares, not installs or unique users. Methodology: FAQ StatCounter, chart licensed under CC BY-SA3.0.

Regular Chrome 154 Stable was released on September 22. Starting with Chrome 153, the major-release cycle was shortened to two weeks. Sources: Chrome 154 release, Chrome release cycle.

Which solutions we tested

We compared public TLS impersonation families, their wrappers and ordinary HTTP clients as controls. This ranks the capabilities tested, not popularity: comparable user-count data is unavailable. Wrappers around the same core are not treated as independent TLS engines.

Solution / tested versionStack and integrationHow the profile is changed
curl-impersonate and active fork, via curl_cffi 0.16.3CLI/libcurl and Python. Convenient for scripts, but the impersonate build is essential, ordinary curl is not equivalent.Ready-made targets, libcurl options, and JA3/Akamai and extra_fp through the wrapper. We measured this family through curl_cffi, without separately running the original fork’s binary.
curl_cffi 0.16.3Python, a requests-like interface, sync/async, requires the bundled native libcurl.impersonate, ja3, akamai, extra_fp. No ready-made chrome154 in the tested version.
uTLS 1.8.2Go, low level. Controls ClientHello, but HTTP/2 and headers must be coordinated separately.Hello profiles, HelloCustom, ApplyPreset, ClientHelloSpec. HelloChrome_Auto in this version points to Chrome 133, not the current Stable.
Go tls-client 1.16.0Go and a native core for wrappers, with TLS and HTTP/2 combined. Requires replacing the HTTP client in your code.Ready-made ClientProfile, PSK variants and custom profiles. The newest ready-made Chrome is 152, there is no profile named 154.
Python tls-client 1.0.1Python, a simple Session API with a native library. The wrapper version does not establish how current the Go core is.client_identifier and custom TLS/HTTP2 parameters. Chrome presets in the tested distribution stop at 120.
noble-tls 0.1.9 / native 1.16.0Python async. Updates native tls-client separately from the Python package, so the core version must be pinned for reproducibility.Preset enum, public client_identifier and custom settings. The loaded core accepts 152/152_PSK even though the wrapper enum lags behind.
CycleTLS 2.0.5Go/Node.js. Node launches a helper process whose lifecycle must be managed.JA3, JA4R and HTTP2Fingerprint. Manual configuration depends on the core’s own extension support.
rnet 2.4.2A native Python client, sync/async. An old distribution cannot automatically be treated as current wreq.Impersonate enum, tested Chrome preset: 137, no Chrome 154 name.
wreq, Python 0.12.3Rust and a Python binding, sync/async, convenient browser/platform selection, but the API differs from requests.Emulation(Profile, Platform), TLS/H2 settings. The latest Chrome in the tested Python package is 153.
wreq-js 3.2.0Node.js/Bun, a fetch-like API and native Rust binding. The wrapper has its own update schedule.browser, os, public TLS/H2 overrides. Ready-made Chrome profiles stop at 149.
primp 2.0.1Python sync/async, a simple client on a native engine. Selecting an OS and browser takes little code.impersonate / impersonate_os, ready-made Chrome 153. The tested constructor has no public full ClientHelloSpec.
got-scraping 4.2.1Node.js ESM, similar to got. The project is declared EOL and recommends impit.Header generation and TLS transport settings. Selecting a version in the header generator does not create a corresponding full ClientHello.
impit 0.14.5Node.js/Python, a fetch-like API and native Rust core. Convenient for new HTTP-client code.browser presets, the tested Node package includes Chrome 151 and rejects Chrome 154.
req/v3 3.61.0Go, high level with a fluent API. TLS and HTTP/2 can be configured through public methods.ImpersonateChrome, SetTLSFingerprint, SetTLSFingerprintSpec, H2 settings. Built-in ImpersonateChrome uses Chrome 120.
httpcloak 1.7.2Go and wrappers, tested through Python. Ready-made presets with convenient JSON export/import.preset, JA3/Akamai, describe_preset and load_preset_from_json. The latest Chrome preset is 152, a custom 154 profile was tested separately.
BlankTrail Proxy 1.3.15A standalone HTTP/SOCKS proxy application for different languages, including legacy and third-party software. Configured at the client level. Impersonating the HTTPS transport requires installing a certificate in the system or ignoring certificate verification in the client.Ready-made TLS/HTTP2/HTTP3 browser profiles, browser/OS selection or matching by User-Agent. The product maintains the profile database, no library needs to be embedded in your code.

An easy API does not remove the maintenance work. If an application already uses requests, switching to another Python client is usually a localized change. A library in a different language may require a binding, a separate service or a transport rewrite. For closed-source third-party software, embedding a new library is often impossible. Updating the package, native core, selected preset and your own headers are four separate tasks.

Comparing libraries with real Chrome 154

The tested distributions had no ready-made Chrome 154 preset as of October 3, so we used the newest available or supported automatic profile with User-Agent154. Exceptions were BlankTrail’s ready-made Chrome_154_win, the got-scraping header generator and manual CycleTLS configuration. Direct selection of 154 and public configuration capabilities were tested separately. Below are the JA4/H2 matches, followed by raw differences those summaries hide.

Selected profiles compared with Chrome 154 by fresh JA4 and HTTP/2. Matching summaries do not mean all fields match, details are in the expandable section.
Selected profiles compared with Chrome 154 by fresh JA4 and HTTP/2. Matching summaries do not mean all fields match, details are in the expandable section.
TLS and HTTP/2 results for each profile
Client / selected profileFresh JA4HTTP/2What still needs checking or fixing
curl_cffi / chrome150DifferentMatchedNo trust_anchors, signature algorithms lack the reference Chrome 154 GREASE.
Go tls-client / Chrome_152 and Chrome_152_PSKMatchedMatchedRaw captures confirmed matches in the stable TLS fields checked, including GREASE, ALPS and 28 trust anchors. Headers were aligned with 154 through the public API. The standard profile produced four full handshakes, 152_PSK produced one full handshake and three accepted resumptions.
Python tls-client / chrome_120DifferentMatchedOld group set and ALPS code, no trust_anchors. The name chrome_154 does not produce a valid new profile.
noble-tls / chrome_152_PSK, core 1.16.0MatchedMatchedRaw captures confirmed matches in the stable TLS fields checked, including GREASE, ALPS and 28 trust anchors. Headers were aligned with 154 through the public API, with three accepted resumptions. There is no ready-made 154 name, and the native core version needs to be pinned.
rnet / Chrome137DifferentMatchedOld signature_algorithms, no trust_anchors. No resumption was observed in four requests.
wreq Python / Chrome153MatchedMatchedtrust_anchors contains an empty list, sec-ch-ua remained at 153 despite User-Agent154.
wreq-js / chrome_149DifferentMatchedNo ready-made 154, public overrides have no parameter for arbitrary trust_anchors contents.
primp / chrome_153MatchedMatchedEmpty trust_anchors, no GREASE in signature_algorithms, different ALPS payload, sec-ch-ua is 153. The response run was incomplete.
got-scraping / Chrome 154 generatorDifferentDifferentBrowser headers do not reproduce Chrome SETTINGS and TLS extensions.
impit / chrome151DifferentDifferentSETTINGS lacks Chrome’s HeaderTableSize and adds MaxFrameSize, repeat TLS also differs.
req / ImpersonateChromeDifferentDifferentTLS preset 120, additional SETTINGS_MAX_CONCURRENT_STREAMS=1000.
uTLS HelloChrome_Auto + Go HTTP/2DifferentDifferentTLS profile 133 and separately configured Go H2. uTLS does not promise automatic HTTP/2 configuration.
httpcloak / chrome-latest-windowsMatchedMatchedOriginal Client Hints refer to 152. The stable TLS fields checked, including a nonempty trust-anchor set, matched reference 154.
CycleTLS / copied Chrome 154 JA3 and H2Connection not establishedThe core returned an error: extension 51764 is unsupported.
BlankTrail / Chrome_154_winMatchedMatchedClient Hints154, the stable TLS fields checked and the transition to resumed JA4 matched the reference.

Control clients requests 2.34.2, HTTPX 0.28.1, Node fetch, Go net/http and system curl 8.13.0 with Schannel also differ from Chrome 154. This is expected: their purpose is to make HTTP requests correctly, not reproduce a browser. Even with HTTP/2 enabled, HTTPX sends its own SETTINGS. Supporting HTTP/2 does not in itself mean matching Chrome.

Now let’s look beyond the JA4 hash

Identical JA4 does not guarantee identical ClientHello: the hash does not compare every extension payload. Below is a combined raw TCP/TLS analysis for the Chrome 154 target.

We compared ciphers, groups, signature_algorithms, TLS versions, key_share groups and lengths, extension sets and stable payloads, including ALPN, ALPS, GREASE and trust_anchors. Random bytes in keys, ECH, tickets and binders were not required to match byte for byte. Permitted reordering was evaluated against the browser reference.

ClientTested profileTLS fieldsRepeat TLSMatches / differences found
BlankTrailChrome_154_win✓✓TLS: The TLS fields checked, GREASE, ALPS and trust_anchors matched.
Repeat: PSK accepted, JA4 transition matched the reference.
Go tls-client152 and 152_PSK✓△TLS: 8 ClientHello records: the fields checked, ALPS and 28 anchors matched.
Repeat: 152: 0/4, 152_PSK: 3/4. The server selected PSK, extension 41 was last.
noble-tlsnative1.16.0, 152 and 152_PSK✓△TLS: 8 ClientHello records: the fields checked and anchors matched.
Repeat: 152: 0/4, 152_PSK: 3/4. PSK acceptance confirmed by ServerHello.
httpcloaklatest✓✓TLS: The fields checked and the nonempty anchor set matched.
Repeat: PSK works in the tested configuration.
wreq PythonChrome153△✓TLS: JA4 matched, but trust_anchors is an empty list, 0000. Signature algorithms and ALPS matched.
Repeat: PSK works, but the empty TLS payload remains a difference.
primpchrome_153✕✕TLS: Empty trust_anchors, no GREASE in signature_algorithms, different ALPS payload.
Repeat: The required PSK transition was not obtained.
curl_cffichrome150✕✕TLS: No trust_anchors or reference 154 GREASE in signature algorithms.
Repeat: The required TCP transition was not reproduced.
Python tls-clientchrome_120✕△TLS: Old groups and ALPS code, no trust_anchors. The name chrome_154 does not create a new profile.
Repeat: Partial, differences in the old TLS profile remain.
rnetChrome137✕△TLS: Old signature_algorithms, no trust_anchors.
Repeat: Original series: 0/4, overall assessment is partial.
wreq-jschrome_149✕△TLS: Old profile, overrides do not set the anchor payload.
Repeat: Partial, the exact target sequence is unconfirmed.
req/v3ImpersonateChrome✕✕TLS: Built-in TLS profile 120 does not match154.
Repeat: The required Chromium PSK transition was not obtained.
uTLS + Go HTTP/2Native transport✕△TLS: HelloChrome_Auto uses profile 133. Anchors can be customized, but no ready-made154 was obtained.
Repeat: Partial, requires separate configuration.
impitchrome151✕✕TLS: TLS profile 151 and the repeat handshake differ.
Repeat: The repeat fingerprint differs.
CycleTLSJA3 + H2 Chrome154✕✕TLS: Chrome 154 JA3: unsupported extension 51764 error.
Repeat: No successful sequence in this mode, Safari tested separately.
got-scrapingheaders154✕✕TLS: Native TLS with browser headers.
Repeat: The browser transition was not reproduced.
Controls: HTTPX, Go net/http, Windows curl, requests, Node fetchNative transport✕△TLS: Native ClientHello, no ready-made Chrome 154 emulation.
Repeat: Go with cache and Node: PSK accepted, nonbrowser TLS. The others did not produce the required transition.

✓ - matched in the tested configuration, ✕ - target result not obtained, △ - partial or profile-dependent. TLS fields are assessed against Chrome 154. PSK acceptance alone does not mean the entire repeat fingerprint matches. Go tls-client and noble-tls results depend on choosing the standard or PSK profile.

The key difference hidden by JA4: wreq/primp trust_anchors contains 0000, while the reference and matching clients have 28 IDs, 186 bytes including the list length. primp’s ALPS is 000403c9bb32, rather than the reference 0003026832 with h2. The anchor count applies to this capture: Chrome Root Store updates independently of the browser version.

Headers were checked separately: wreq/primp combined User-Agent154 with Client Hints153. These can be aligned through the public API, but that will not fix the empty TLS payload.

Can you move to a newer browser version without rewriting the library?

Having no preset named Chrome 154 is not the same as being unable to reproduce its behavior. We tested direct profile names, available public parameters and several manual configurations.

  • curl_cffi: impersonate="chrome154" is rejected. Passing the reference JA3 and Akamai values alongside chrome150 completed the request, but the wire still showed the old profile’s JA4 without trust_anchors. Accepting a configuration does not mean applying it fully.
  • Python tls-client: the string chrome_154 did not produce a correct154 profile, although creating Session did not report a missing profile. Inspect the wire, not just whether the constructor succeeds.
  • Go tls-client / noble-tls: there is no ready-made154 name, but raw captures confirmed that the stable TLS fields checked in profile 152 matched reference 154, and measured H2 matched as well. The152_PSK variant reproduced resumption, while supported header settings aligned HTTP with 154. This result required no core source changes. The custom ClientProfile/API can extend the configuration, but this test does not prove it can reproduce every future version.
  • wreq / primp / rnet: direct selection of 154 is rejected or absent from the enum. An old preset plus a new User-Agent does not provide a full match. Public settings can change some behavior, but the ability to populate a specific missing extension must be checked separately. primp has no public full ClientHello constructor.
  • wreq-js / impit: unknown Chrome 154 returns an error. wreq-js offers TLS/H2 settings, but the tested API has no arbitrary trust_anchors payload. impit obtains ready-made profiles from its native catalog.
  • CycleTLS: manual JA3/JA4R and H2 are supported, but this Chrome 154 attempt stopped at unsupported extension 51764. A fingerprint string alone cannot build the required ClientHello here.
  • uTLS / req: the public ClientHelloSpec and GenericExtension allow extensions to be defined manually, and req provides HTTP/2 configuration. This is a supported route without library changes, but the developer must maintain the custom profile, aligned headers and resumption checks. We do not present a full manual Chrome 154 implementation for this pair as a measured result.
  • got-scraping: the header generator accepts a154 version constraint, but measured TLS/H2 still differ. Reaching the current browser transport takes more than generator settings.
  • httpcloak: no ready-made chrome-154 exists. We exported the latest profile through describe_preset, registered custom JSON with aligned154 headers through load_preset_from_json, and made four requests. Fresh and resumed JA4, H2 and the TLS fields checked matched. This is a confirmed example of configuration without rewriting the library.

Some libraries therefore do let you assemble a current profile yourself. The difference from a ready-made product is who gathers reference captures, verifies fields, aligns HTTP and handles future updates.

TLS resumption: checking session-resumption behavior

A server can issue a session ticket, after which the client opens a new connection and attempts to resume the TLS session. In TLS1.3 this changes ClientHello: pre_shared_key appears with a real binder and must be the last extension. Other extensions may also disappear. Reusing the same “fresh” template for every connection is not the same as reproducing browser-session behavior. The mechanism is described in RFC 8446, pre_shared_key.

Reference Chrome 154 has the fresh JA4 t13d1517h2_8daaf6152771_cb7bf5808d99, and on resumption t13d1518h2_8daaf6152771_e2d80978ab2e. Extension41 is added, while the basic HTTP/2 summary stays unchanged. BlankTrail produced one fresh request and three accepted resumptions with this transition. Go tls-client152_PSK, noble-tls with that core, wreq Chrome 153, primp in the records obtained, and httpcloak showed the same transition in summary values.

The standard Go tls-client Chrome_152 without the PSK profile produced four fresh handshakes in our run. This does not mean the core cannot resume sessions: switching to supported Chrome_152_PSK changed the result. Capability and the configuration of a particular mode must be assessed separately.

Fresh and resumed TLS in Chrome 154 and Firefox 147, and the different repeat ClientHello from curl_cffi.
Fresh and resumed TLS in Chrome 154 and Firefox 147, and the different repeat ClientHello from curl_cffi.

Firefox shows a subtler difference. Reference Firefox 147 adds41 and removes session_ticket 35 on resumption: JA4 still counts17 extensions, while the fresh suffix 3cbfd9057e0d changes to e6dcd7ae0a9e. curl_cffi firefox147 retained35 and added41, giving18 extensions and the suffix 5dff20295fdc. Despite a correct fresh fingerprint, its repeat connection differed from the reference used. BlankTrail Firefox 147 reproduced adding41 and removing35.

Failure to resume in a short run depends on ticket policy, settings and cache. We therefore considered server acceptance of PSK under comparable conditions, not merely the presence of extension 41.

Let’s test how the libraries handle HTTP/3

HTTP/3 runs over QUIC/UDP and was tested separately from TLS/H2: complete ClientHello reconstructed from Initial, transport parameters, Initial/CID, SETTINGS, QPACK and header order. Tests included zero and nonzero QPACK capacity, new connections, PSK and early HTTP requests. The completeness of byte-level analysis is stated for each configuration.

References were installed Chrome 154, Edge 154.0.4258.48 and Firefox 156.0.1 on Windows, Safari 26.6.2 on a MacBook, plus captures from physical Android and iPhone devices. 

Real browserFresh QUIC JA4Key reference details
Chrome154q13d0312h3_55b375c5d22e_178839b6cec1trust_anchors present, Initial 1250 bytes, DCID8 / SCID0.
Edge154q13d0311h3_55b375c5d22e_653d80c3fe9dNo trust_anchors, Initial 1250, DCID8 / SCID0. Chrome with changed headers does not become Edge.
Firefox156q13d0315h3_55b375c5d22e_bb76f32061e3Its own TLS and QUIC parameter set, Initial 1252, DCID8 / SCID3.
Safari 26.6.2 macOSq13d0311h3_55b375c5d22e_f2a83c8e78aeInitial 1200, DCID8 / SCID0, H3 SETTINGS: QPACK capacity 16383, blocked streams100 plus GREASE.

We normalized random GREASE values, key data and connection IDs. trust_anchors was compared by identifiers, not random order, as Root Store can update independently of the browser number. The Chrome QUIC reference does not contain the signature_algorithms GREASE present in its TCP ClientHello: rules from one protocol cannot be applied to another.

HTTP3 protocol support and browser-fingerprint accuracy are separate checks. Results apply to the configurations measured.
HTTP3 protocol support and browser-fingerprint accuracy are separate checks. Results apply to the configurations measured.

How to read this: ✓ - success for the stated criterion, ✕ - a difference or unmet target, △ - partial results or limited confirmation. TLS, TP, Initial/CID, SETTINGS and QPACK are compared with Chrome 154 in the specified configuration, differences for other browser profiles are listed on the right. When H3 does not work, the remaining ✕ indicate unavailable results, not measured defects in every field. For0-RTT rejection, △ means a response without an early-data attempt, so recovery is unconfirmed.

Solution / modeH3 worksTLS fieldsQUIC TPInitial / CIDSETTINGSQPACKHTTP in 0-RTT0-RTT rejectionDefects and test outcome
curl_cffi
V3ONLY / chrome150
✓✕△✕✓△✕△No trust_anchors, Initial 1200 instead of 1250, no early HTTP. Edge 101, Firefox 147 and Safari 260 also show raw differences from the target versions.
Go tls-client
protocol racing / 152_PSK
✓✕✕✕✕✕✕✓Extra TLS extensions, Initial 1280, different CIDs, SETTINGS only 51=1, QPACK encodes ?0 differently. 1+3 instead of 2+2, HTTP is not early.144_PSK improves SETTINGS, not the whole fingerprint.
noble-tls
native1.16.0 / 152_PSK
✓✕✕✕✕△✕✓Different ClientHello, CID and SETTINGS, QPACK has not been separately confirmed in full. Protocol racing duplicates the first GET over H2/H3, HTTP is not early.
httpcloak
http_version=h3 / latest
✓✓✓✓✓△✕✕No defects found in the Chrome TLS/TP/Initial/SETTINGS checked, full QPACK accuracy unproven. Errors after q/qd idle and on 0-RTT rejection. Firefox differs, Safari iOS is limited by its corresponding reference.
impit
forceHttp3 / chrome151
✓✕△✕✕△✕△Different ciphers/extensions, Initial 1200, CID20/8, SETTINGS includes MAX_FIELD_SECTION_SIZE=2⁶²−1. PSK works, no early HTTP. Firefox 144 and Chrome with Edge UA also failed to reproduce the target.
CycleTLS
forceHTTP3 / custom JA3
✓✕△✕△△✕△JA3 does not reproduce QUIC: different extensions, groups, signatures, Initial 1280 and different CIDs. A new fresh connection for each GET, no PSK/0-RTT.
req/v3
EnableForceHTTP3
✓✕△△✕△✕△Different TLS groups/signatures, extension 50, SETTINGS6=10485760. New connection without PSK after server closure, no early HTTP.
wreq Python
HTTP_3
✕✕✕✕✕✕✕✕H3 not established: NO_APPLICATION_PROTOCOL. No successful HTTP/3 fingerprint to compare.
rnet
HTTP_3
✕✕✕✕✕✕✕✕H3 not established: UserUnsupportedVersion.
wreq-js, primp, Python tls-client, HTTPX, requests, Node fetch, got-scraping
Tested packages/APIs
✕✕✕✕✕✕✕✕No working QUIC in the tested configurations, requests stayed on TCP/H2 or H1.
uTLS + Go HTTP/2, Go net/http
Tested transports
✕✕✕✕✕✕✕✕TCP transports, H3 requires another QUIC/HTTP3 client.
Windows curl
Schannel / --http3-only
✕✕✕✕✕✕✕✕This build does not support HTTP/3 and rejects the flag. This does not apply to curl_cffi.
BlankTrail Proxy
1.3.15 / six profiles
✓✓✓✓✓✓✓✓No defects found in the fields and scenarios checked.342 QUIC connections in the main series, the current behavior test has72 successful requests and 36 connections. QPACK/GREASE and early HTTP confirmed, live0-RTT rejection tested on Chrome.

A closer look at raw HTTP/3 bytes

ClientHello was assembled from every CRYPTO fragment in QUIC Initial. The library byte-level sample contains25 connections and 54 header blocks, with TP order, windows, stream limits, Initial and CID preserved.

SETTINGS was saved before conversion to a map, QPACK before decoding into name/value pairs. This preserves the actual order and encoding method, which ordinary JSON logs can hide.

✓ - the fields checked match the stated reference, ✕ - a difference found, △ - limited confirmation, not counted as a full match. Marks apply to a specific profile and sample. In the values below, max means 2⁶²−1.

ConfigurationReferenceTLS fieldsQUIC TPInitial / CIDSETTINGSQPACKSpecific differences and outcome
curl_cffi / Chrome150Chrome154✕△✕✓△No trust_anchors51764, Initial 1200 instead of 1250. Stable SETTINGS and their order matched. Full QPACK accuracy unconfirmed.
curl_cffi / Safari260Safari 26.6.2 Mac✕△✕✕△Different groups, key_share and supported_versions. CID20/20 instead of 8/0, SETTINGS6=max →1=0 →7=0 instead of QPACK capacity 16383 and blocked streams100.
curl_cffi / Edge101Edge154✕△△✕△Different TLS, SETTINGS6=max →1=0 →7=0. The outdated preset did not reproduce the current target.
curl_cffi / Firefox147Firefox156✕✕△△△Different ciphers, groups and QUIC parameters. Other fields lack full confirmation.
Go tls-client / 152_PSKChrome154✕✕✕✕✕TCP-like TLS extensions, Initial 1280. SETTINGS only 51=1 instead of 1/6/7/51 + GREASE. QPACK encodes ?0 with a different Huffman flag.
Go tls-client / 144_PSKChrome154✕△△✓✕SETTINGS and order matched, TLS/QUIC154 did not. QPACK uses a different Huffman flag for ?0. The older preset fixes only some fields.
noble-tls / native1.16.0Chrome154 / Safari26✕△✕✕△Different ClientHello, CID and SETTINGS. Full wrapper QPACK is not independently confirmed, Go results cannot automatically be applied to it.
httpcloak / Firefox-latestFirefox156✕✕△△△Different TLS. TP windows4=15728640 and 5/6/7=6291456 instead of 25165824,12582912,1048576,1048576.
httpcloak / Chrome-latestChrome154✓✓✓✓△No defects found in the TLS fields, TP sets, Initial/CID and SETTINGS checked. Full QPACK accuracy unconfirmed, behavioral errors are in the main H3 table.
httpcloak / Safari iOSLimited Mac reference△△△△△QUIC windows differ from measured macOS Safari. This byte-level sample had no separate iPhone reference, so this is not a proven iPhone-profile defect.
impit / Chrome151Chrome154✕✕✕✕△15 ciphers instead of 3 TLS1.3 ciphers, TP4=max,5/6/7=1250000 instead of 15728640 and 6291456. Initial 1200, different SETTINGS.
CycleTLS / Chrome154 JA3Chrome154✕✕✕△△Different ClientHello, TP4=1048576,5/6/7=524288 instead of 15728640 and 6291456. Initial 1280. Custom JA3 did not become an exact QUIC profile.
req/v3 / ForceHTTP3Chrome154✕✕△✕△Different TLS, including extension 50. TP4=786432, streams_bidi8=0 instead of 100. SETTINGS only 6=10485760 instead of 262144 and 1/7/51/GREASE.
BlankTrail / 1.3.15Six tested profiles✓✓✓✓✓No defects found in the fields checked: TLS, normalized TP, Initial/CID, SETTINGS and QPACK match saved references. Safari: fresh/PSK/0-RTT, Initial 1200, Chromium: encoder stream 10.

A protocol-valid parameter can still differ from a browser: Initial 1200 or a different QUIC window, for example, is not a protocol error, but prevents reproducing the selected reference. This does not prove inevitable antifraud detection, but shows it is potentially possible.

SETTINGS: a concrete mismatch
Chrome154 / Edge154: 1=65536 → 6=262144 → 7=100 → 51=1 → GREASE
Go tls-client / 152_PSK: 51=1
req/v3 / ForceHTTP3: 6=10485760

Missing parameters in 152_PSK cannot be explained by GREASE shuffling. ID/value pairs and their order were compared before conversion to a map. The format is described in RFC9114, and instructions for capturing them yourself are below.

QPACK: identical headers can be encoded differently

In real Chrome 154 and Edge 154, the header sec-ch-ua-mobile: ?0 is sent without Huffman encoding of the value, whereas Go tls-client152_PSK and 144_PSK use it. Both are valid, but the bytes differ from the reference.

Such a comparison requires identical header values and request context. We did not count curl_cffi’s platform difference as a defect because macOS and Windows were being compared. The original54 QPACK blocks were captured at capacity 0. The dynamic table and encoder/decoder streams were tested separately at nonzero capacity under RFC9204.

HTTP/3 behavior was tested with four GETs in one session, separated by2,42 and 2 seconds, with automatic and forced H3 selection: q/qd - no 0-RTT, QPACK capacity 0/65536, qz -0-RTT allowed.

PSK, accepted0-RTT and an early HTTP request are three different results. The pre_shared_key extension only offers resumption. The server separately confirms PSK and early-data acceptance, and we separately check whether the stream carrying the GET itself opened in 0-RTT. For Go tls-client and noble-tls, the server accepted early data, but the GET was sent after the handshake. The used_0rtt flag alone is therefore insufficient.

ClientConnection selection and lifecycleHTTP in 0-RTT0-RTT rejectionOutcome and limitations
BlankTrail ProxyChrome/Edge: H3 immediately,2+2 requests. Firefox: TCP first. PSK after idle.✓✓All6 profiles sent early HTTP.72 requests without errors,36 QUIC connections with no differences in the raw fields checked.
curl_cffiH3 immediately in V3 mode,2+2 requests, PSK after idle.✕△The tested Chrome 150 profile did not offer early data. Raw differences from 154 remain.
Go tls-clientH3 immediately,1+3 requests: a new resumed connection for the second GET, then retained through idle.✕✓Server accepted0-RTT, but the HTTP request was not early. Raw TLS/QUIC and SETTINGS differ.
noble-tlsThe same1+3 H3 sequence, with the first GET also sent over H2.✕✓In three modes, the first GET was duplicated over H2 and H3 with protocol_racing=True. No early HTTP, raw QUIC differences remain.
httpcloakauto: all requests over TCP. Forced H3: errors after q/qd idle, qz gives2+2.✕✕4 errors in 24 ordinary attempts. Error on 0-RTT rejection, not resolved by two supported retries.
impitH3 immediately with http3=True,2+2 requests, PSK after idle.✕△Early data not offered, raw differences from the selected browser remain.
req/v3auto: first request over TCP, then H3. Forced: one H3 connection retained through idle.✕△After forced server closure, the new connection was also fresh: no PSK/0-RTT in the tested configuration.
CycleTLSauto: TCP. Forced H3: a new connection for each of four requests.✕△No PSK or early data. Requests succeed, but reuse and raw fields differ from Chrome/Edge.

In the “0-RTT rejection” column, ✓ means successful delivery after a rejected early-data attempt, ✕ an error, and △ a successful request without attempting0-RTT, so this client’s recovery mechanism was not tested. The server retained ticket keys and PSK capability, closed the previous connection, but disallowed early data. BlankTrail, Go tls-client and noble-tls received responses over resumed connections without accepted0-RTT. httpcloak failed with 0-RTT rejected, and the next request with tls: illegal parameter. Supported retry=2 did not recover the request either.

A duplicate GET was found in noble-tls. In each of three automatic modes, the server handled the same first URL over both HTTP/2 and HTTP/3: it received five requests instead of the four planned. Direct Go tls-client had no duplicate in this series. The result applies to GET and protocol_racing=True, POST behavior was not tested.

HTTP/3 connection lifecycles in an identical four-request scenario. Full conditions and limitations are in the table.
HTTP/3 connection lifecycles in an identical four-request scenario. Full conditions and limitations are in the table.

In the same fetch scenario, real Chrome and Edge made two fresh H3 requests, then after idle two requests on a new resumed connection, the first being early in qz. BlankTrail with the corresponding profiles repeated that sequence. For Safari and Firefox, initial transport selection depended on saved connections and origin coalescing: Safari retained TCP in q, and used H3 in qd/qz. An initial TCP request is therefore not a universal defect in all browser profiles.

The current BlankTrail run tested Chrome 154 Windows/Android, Edge 154, Firefox 156 and Safari 26 macOS/iPhone. All72 requests completed, and the stable TLS fields checked, transport parameters, Initial/CID, SETTINGS, control streams and QPACK matched across36 QUIC connections. Early HTTP in qz was obtained for all six profiles. The run and 0-RTT rejection test used build 1.4.1064, whose changes are intended for release1.3.15. This confirms specific scenarios, not the frequencies of every permutation or behavior under all network conditions.

Edge and Firefox: testing the current target, not the preset name

In HTTP/2 scenarios, we requested Edge 154 and Firefox 156 using available profiles. curl_cffi edge101 and rnet Edge 134 differ from Edge 154 in TLS, wreq-js Edge 148 is not a ready-made154. primp Edge 151 passed brief detection, but raw signatures and ALPS differ. httpcloak’s Chrome profile with correct Edge headers retained an extra trust_anchors. Firefox 147/148/151 library profiles do not count as full156 matches: raw comparisons show different ciphers/groups. A clean brief JSON does not override that difference.

Also checking Safari, iOS and Android profile quality

A mobile User-Agent does not replace a mobile transport. This comparison covers Safari 26 macOS/iPhone and Chrome 154 Android, not just preset names.

ClientSafari macOSSafari iPhoneChrome AndroidSafari: results and differencesAndroid: results and differences
curl_cffi✕✕✕safari260 / safari260_ios: HTTP/2 matched, TLS differed. The iOS preset had session_ticket 35 and padding21, absent from the fresh reference used.chrome131_android: old TLS class, did not match target 154.
wreq Python△△△Safari 26 and SafariIos26: fresh JA4/H2 matched. No accepted resumption observed in four requests per preset.Chrome 153 + Platform.Android: JA4/H2 matched class 154, but trust_anchors was empty and Client Hints remained 153.
wreq-js△△✕safari_26 / safari_ios_26: fresh JA4/H2 matched, standard preset did not resume. A separate attempt to enable PSK through public overrides did not match the reference profile.chrome_149 + android: HTTP/2 matched, fresh TLS differed.
primp△△△safari_26 + macos/ios: JA4/H2 matched in collected records, resumption observed, raw ClientHello differs in GREASE presence, order and some field contents. Responses incomplete.chrome_153 + android: summaries matched, but stable TLS fields and Client Hints version still differ.
Go tls-client 1.16.0✕△△Direct Go API: safari_ios_26_0 produced matching fresh JA4/H2, no PSK in four requests. safari_16_0 claiming macOS Safari 26 differs in TLS and H2, no ready-made macOS26 in the tested catalog.chrome_131 with mobile 154 headers: TLS differs, H2 matches. Chrome_152_PSK with aligned Android 154 headers: fresh and resumed JA4/H2 matched, stable TLS fields checked matched the reference transport class used.
noble-tls / native 1.16.0✕△△safari_ios_26_0: fresh JA4/H2 matched, no PSK in the short run. Old macOS safari_16_0 did not match target 26.chrome_131 with mobile UA: fresh TLS did not match. Supported chrome_152_PSK with aligned Android 154 headers produced matching fresh/resumed JA4/H2 and stable TLS fields checked.
httpcloak✕△△For iOS latest, the TLS summary matched Safari 26, but H2 differed in SETTINGS order, window and pseudo-headers. safari-latest-macos does not exist, this test did not confirm a separate macOS26 profile.chrome-latest-android: JA4/H2 matched class 154, HTTP headers from original preset 152 need updating.
impit✕✕✕Safari absent from the tested Node package’s Browser enum.chrome151 with mobile UA: TLS/H2 differed, no separate Android-platform parameter in this test.
BlankTrail✓✓✓Safari_26_macOS and Safari_26_iPhone: TCP/H2 retains the fresh WebKit fingerprint without PSK. In HTTP3,41 appears after idle and JA4 changes as in the Safari 26.6.2 QUIC reference.Chrome_154_and: fresh and resumed JA4/H2 matched, stable TLS fields checked matched the Chrome 154-class reference used.

Targets: Safari 26 and Chrome 154 Android. ✓ - matched in the tested configuration, ✕ - target profile not reproduced, △ - partial match or configuration required. JA4/H2 matches without raw-field confirmation do not earn green. Supported manual configuration of Go tls-client and noble-tls matched Android TCP/TLS fields checked, but does not cover the entire emulation, including HTTP/3.

Safari: real26.6.2 did not use PSK in the new TCP connections tested, but resumed QUIC. Absence of TCP resumption is therefore not a defect, whereas PSK in curl_cffi and primp’s Safari scenarios differs from measured behavior.

iOS: BlankTrail’s tested CriOS/FxiOS/EdgiOS paths use WebKit/Safari transport. Requiring desktop Chromium/Gecko ClientHello is incorrect, classifier warnings are separated from client defects.

Android: physical Chrome 154 confirmed PSK, early HTTP in 0-RTT and QPACK encoder stream 10. BlankTrail reproduced the fields and scenarios checked, library-profile limits are in the table.

Detailed Safari/iOS/Android captures, GREASE and QPACK

Safari repeat TCP/H2 connections: one test across all clients

Safari was tested with all 20 solutions, including ordinary transports with a Safari User-Agent where no preset existed. Client and cache were retained between requests, server logs confirmed new connections.

Real Safari 26.6.2 offered no TCP PSK in either safaridriver or a normal window after forced closures. The control Go client resumed on the same server. Absence of Safari TCP resumption is therefore not penalized by itself: it is assessed together with fresh TLS/H2 accuracy. Old Safari 16 without PSK does not become Safari 26.

Client / available Safari profileNew connections / PSKConclusion
BlankTrail: Safari 26 macOS / iPhone4+4, PSK0, accepted0Repeat full TLS/H2 handshakes match the selected Safari 26 class, the confirmation gap is closed.
Go tls-client: iOS26, macOS164+4, PSK0iOS26: fresh summaries match26, macOS16 retains an outdated TLS profile.
noble-tls: iOS26, macOS164+4, PSK0Same core result, no PSK does not fix the old macOS preset.
wreq26.4 / wreq-js26.44+4, PSK0Fresh Safari summaries and absence of PSK match selected behavior, without removing other raw-data limitations.
httpcloak Safari iOS4, PSK0Repeat full handshake, measured H2 difference from Safari remains.
rnet Safari18.54, PSK0Fresh summary matches class 26, preset age and raw differences remain.
curl_cffi Safari2604, PSK offered/accepted3Fresh TLS already differs, three repeat ClientHello resumed, unlike the selected Safari 26.6.2.
primp Safari262 connections from 4 attempts, PSK1 acceptedRepeat ClientHello contains41 and the server accepted resumption, two other responses were lost on H2 closure. The incomplete-run count is disclosed.
Python tls-client Safari15.6.14, PSK0Outdated fresh TLS does not match26.
impit, CycleTLS, req/v3, uTLS+GoH2, got-scraping4 responses each, exact Safari 26 not obtainedTested fallback/ordinary transports do not replace a missing current Safari preset. PSK in nonbrowser transport is not treated as a separate Safari-profile defect.
requests, HTTPX, Go net/http, Windows curl, Node fetch4 attempts each, HTTPX received2 responsesControls with Safari User-Agent, Safari emulation neither claimed nor obtained.

iOS: browser name and transport engine

For BlankTrail1.3.15, we measured database filters chrome+ios, firefox+ios, edge+ios and safari+ios, plus User-Agent selection for CriOS, FxiOS, EdgiOS and EdgiOS on iPad. All eight paths produced one WebKit/Safari transport class. Different UA brands and versions do not mean an iOS profile should send desktop Chromium or Gecko ClientHello.

Profile-selection pathNew TCP connectionsMeasured TLS/H2PSK41
database chrome+ios / firefox+ios / edge+ios / safari+ios3 each,12 totalSafari JA4 t13d2013h2_a09f3c656075_7f0f34a4126d, H2 m,s,a,pAbsent in all 12
By UA: CriOS / FxiOS / EdgiOS / EdgiOS iPad3 each,12 totalSame Safari JA4 and H2Absent in all 12
Chrome Windows control3Fresh t13d1517h2…cb7bf5808d99 → PSK t13d1518h2…e2d80978ab2e, Chromium H2 m,a,s,pPresent in two repeat ClientHello records

All24 TCP records across eight iOS paths produced H2 2:0;3:100;4:2097152;9:1|10420225|0|m,s,a,p. UAs contained the appropriate CriOS/FxiOS/EdgiOS tokens. This tests the product’s profile selection, not24 physical iPhone/iPad runs.

In the current iOS run, research /client received24 responses:21 with CriOS/FxiOS/EdgiOS contained engine_mismatch, and CriOS/EdgiOS also had client_hints_absent. On the public /client , eight scenarios were tested separately: seven reproduced those false warnings, Safari passed. Server records show expected WebKit/Safari TLS/H2. Demanding desktop Chromium/Gecko and desktop Client Hints here is a detector-classification error, not an identified product defect.

Safari HTTP/3: fresh and PSK forms need separate references

Unlike TCP, the saved Safari 26.6.2 QUIC reference contains a fresh form and two resumption variants, with and without 0-RTT. BlankTrail reproduced these states while retaining cache after idle.

ScenarioQUIC JA4ExtensionsInitial
Live Safari 26.6.2: freshq13d0311h3_55b375c5d22e_f2a83c8e78aeNo41/421200
Live Safari: PSK without 0-RTTq13d0312h3_55b375c5d22e_151122171f7d41 last, no 421200
Live Safari: PSK with 0-RTT allowedq13d0313h3_55b375c5d22e_6bb9a3ac9a4b42 between45 and 43,41 last1200
BlankTrail Safari 26 macOS / iPhoneEach profile: fresh…f2a83c8e78ae → PSK…151122171f7dOrder,41 last and absence of 42 matched the chosen reference variant1200 in all 4 QUIC connections

For Safari macOS/iPhone, the ciphers, groups, signature algorithms, versions, key_share form, stable extension payloads and TP values compared matched QUIC references after random-data normalization.

Fresh wire order:

GREASE,0,10,16,5,13,18,51,45,43,57,27,GREASE

PSK without 0-RTT:

GREASE,0,10,16,5,13,18,51,45,43,57,27,GREASE,41



Observed transport parameter orders:

14 15 4 5 6 7 9

9 14 15 4 5 6 7

4 5 6 7 9 14 15

Safari TP order uses cyclic rotations of 4,5,6,7,9,14,15, not arbitrary shuffling. Every product order captured belongs to this family.

Safari macOS and iPhone confirmed not only tls_resumed and used_0rtt, but also the HTTP request bytes in early data. With delayed server responses, Early-Data:1 was the last header of the repeat request and absent from the fresh request.

In the current BlankTrail run, all 342 QUIC connections across six profiles passed the listed checks of stable TLS fields, TP, SETTINGS, Initial/CID and QPACK streams. Chromium sends12583 after accumulating origin RTT, Firefox retains fixed0xff02de1a (min_ack_delay), which must not be treated as random GREASE.

H3 measurement conditions:

h2_spoofing, enable_http3 and spoof_headers enabled, explicit profile, UA taken from the catalog. The original client passes context through Sec-Fetch-Dest/Mode/Site.

The first QPACK block depends on when SETTINGS arrives. In six control runs of real Chrome 154.0.8037.95, the first block was dynamic, and NetLog confirmed SETTINGS before headers. A dynamic first block from the product is therefore not a defect by itself.

HTTP/3 structure, GREASE and QPACK across six profiles

Shuffling was tested on 300 fresh HTTP/3 connections:50 each for Chrome 154 Windows/Android, Edge 154, Firefox 156 and Safari 26 macOS/iPhone. Each profile used server QPACK capacity 0 for 25 connections and capacity 65536 for 25.

Item checkedChrome / EdgeFirefoxSafari macOS / iPhone
Stable TLS fields and SETTINGSMatched in all 100 connectionsMatched in all 50 connectionsMatched in all 100 connections, macOS compared with a separate reference
Initial and CID1250/1250, DCID8, SCID01252/1252, variable DCID, SCID31200/1200, DCID8, SCID0
TP and TLS ordering50 distinct TP orders each, GREASE and QUIC version order varyFixed TP, TLS shuffled with 57,65037 at the endAll three permitted TP rotations, no other orders
Frames after SETTINGSGREASE payload0–3 bytes and PRIORITY_UPDATEGREASE payload0–7 bytes, no PRIORITY_UPDATENo extra frames
QPACK at capacity 0Static block, control stream onlyStatic block, control and both QPACK streamsStatic block, control stream only
QPACK at capacity 65536Insertions, dynamic references, encoder stream 10Insertions and both QPACK streamsEncoder sends server capacity, request stays static

Chromium produced50 distinct TLS and TP orders per profile and both available_versions orders. Firefox had50 TLS orders with fixed TP, Safari had fixed TLS and three reference TP orders. All300 fresh connections passed structural checks. PSK and early HTTP were tested on repeat connections.

For Edge and Firefox, eight two-sample KS tests using saved100 and 120 browser connections at a0.01 significance level did not reject equality of individual distributions, minimum p=0.029. This does not prove equality of the joint distribution. The current run checks structure and variability without repeating those KS calculations.

Chrome 154 Android: comparison with a physical device

Physical Android 154 captures include fresh and resumed q/qd/qz: q/qd accepted PSK without 0-RTT, qz accepted PSK and 0-RTT with confirmed early HTTP.

Android and BlankTrail matched on the TLS fields checked, normalized TP, stable SETTINGS and header order. Initial 1250/1250, DCID8, SCID0.

ScenarioReal Android Chrome 154BlankTrail1.3.15
qd: dynamic tableControl stream 2, QPACK encoder stream 10Control stream 2, QPACK encoder stream 10 - matched
qz: static formControl stream 2 onlyControl stream 2 only - matched
qz: repeat after idlePSK/0-RTT accepted, request in 0-RTTPSK/0-RTT accepted, request in 0-RTT - matched

Chromium encoder stream 10 is confirmed by real desktop Chrome and Android. BlankTrail reproduced it for Chrome Windows, Edge Windows and Chrome Android in 25 fresh dynamic-table connections per profile and in repeat connections.

BlankTrail: browser transport for legacy and third-party software

Linux curl7.74.0 through BlankTrail produced the checked Chrome 154, Edge 154, Firefox 156 and Safari 26 profiles. The old client was unchanged: the proxy builds the selected TLS/H2/H3 transport.

Measured scenario: old curl through BlankTrail gets the selected browser TLS/H2 transport.
Measured scenario: old curl through BlankTrail gets the selected browser TLS/H2 transport.

Advantage: connect different stacks and third-party software without source access or native-library integration. Drawback: a separate application to install, run and maintain. For HTTPS impersonation, the client must trust the CA, certificate pinning can prevent operation.

The article uses version 1.3.15. BlankTrail’s stated policy is to update the database no later than five days after a stable browser release. This study checks the listed sample from the catalog’s841 profiles, while the entire catalog was captured and tested against corresponding real browser versions on physical devices and the required OSes.

TCP test parameters and measured JA4/H2

Tested with Linux curl7.74.0 and OpenSSL: the same client through BlankTrail produced Chrome 154, Edge 154, Firefox 156, Safari 26 and earlier profiles. Each profile used a new test port with an empty TLS cache. H2 spoofing and session resumption were enabled, HTTP/3 and browser_mode disabled. After changing profiles, the test restarted, tickets from the previous profile were not used as a “fresh” connection.

Below are measured fingerprints for three BlankTrail profiles. HTTP/2 is written in Akamai format: SETTINGS in send order, connection WINDOW_UPDATE, priority summary and pseudo-header order. The symbols m, a, s, p mean :method, :authority, :scheme, :path. Chrome/Firefox retain HTTP2 after TLS resumption, Safari after another full handshake.

The catalog contains841 profiles. Live tests cover the listed sample, not the whole catalog.

Chrome_154_win

fresh:   t13d1517h2_8daaf6152771_cb7bf5808d99

resumed: t13d1518h2_8daaf6152771_e2d80978ab2e

HTTP/2:  1:65536;2:0;4:6291456;6:262144|15663105|0|m,a,s,p



Firefox_156_win

fresh:   t13d1517h2_8daaf6152771_3cbfd9057e0d

resumed: t13d1517h2_8daaf6152771_e6dcd7ae0a9e

HTTP/2:  1:65536;2:0;4:131072;5:16384|12517377|0|m,p,a,s



Safari_26_macOS

fresh:   t13d2013h2_a09f3c656075_7f0f34a4126d

repeat:  t13d2013h2_a09f3c656075_7f0f34a4126d (full handshake; no PSK)

HTTP/2:  2:0;3:100;4:2097152;9:1|10420225|0|m,s,a,p

How to repeat the comparison

  1. Capture the real target browser and the client under test at tls.peet.ws/api/all, and save versions, profile, OS and JSON.
  2. Get brief detection at BlankTrail /client. It helps identify contradictions, but does not establish byte-for-byte equality.
  3. Compare the actual protocol, TLS fields, SETTINGS and headers. For matching JA4, inspect extension payloads, including trust_anchors.
  4. Test new connections with the session cache retained. For H3, distinguish PSK, accepted0-RTT and the early HTTP request itself.
Detailed instructions: pcap, session keys, QPACK and 0-RTT rejection

BlankTrail /client provides a brief result: recognized popular client, claimed browser and detected contradictions. Save JSON, client version, preset, OS and date. This is a convenient first step, no findings does not prove all bytes match.

  1. Get an independent detailed result. Open tls.peet.ws/api/all with the real target browser, then request the same URL using the library. Save both JSON results. The service is described in the TrackMe repository, /api/all returns collected TLS and HTTP fields. Do not compare browser navigation with a library fetch without accounting for HTTP-header context.
  2. Start with the transport. Check http_version and negotiated ALPN. For TLS, compare ciphers, extensions, supported_versions, supported_groups, signature_algorithms, key_share, ALPS and compression. Use JA3/JA4/PeetPrint as summaries, inspecting extension contents separately, including trust_anchors, when hashes match. For HTTP2, compare akamai_fingerprint and sent_frames: SETTINGS, WINDOW_UPDATE, pseudo-headers and regular headers. Available fields depend on the protocol actually used.
  3. Do not treat expected randomness as a defect. Normalize GREASE values while preserving counts and positions. For Chrome/Edge, capture several new connections and check shuffling. random, session ID, key_share key bytes, tickets and binders should not repeat. Compare key groups and lengths, and trust_anchors against the allowed ID set for the Root Store version. Check reference-fixed order separately.
  4. Capture traffic yourself for actual raw bytes. /api/all provides parsed fields, not a full pcap for every request. Capture your client’s TCP443 and UDP443 in Wireshark. TCP ClientHello is parsed from TLS records and handshakes, QUIC ClientHello must be reconstructed from Initial CRYPTO fragments, not one UDP packet treated as complete TLS. HTTP2/HTTP3 require session keys: enable key logging in a client that supports it, then configure the file in Wireshark’s TLS settings. Instructions are in the Wireshark TLS documentation, QUIC fields in the dissector reference. Public JSON does not replace this step.
  5. Repeat with fresh and new connections in the same session. Keep the client session cache, wait for the ticket, and close the connection on your server while retaining ticket keys. A new request on old H2/H3 is not resumption. PSK41 in ClientHello is an offer, confirm acceptance through the selected PSK identity in ServerHello or server DidResume. For Safari 26.6.2, separate TCP/H2 from QUIC/H3. In the chosen TCP experiment, new connections had no 41. The QUIC reference resumes: without 0-RTT only 41 is added, with 0-RTT allowed42 is also added. Compare the corresponding protocol and server policy.
  6. Test HTTP3 separately. Enable supported H3 mode, use Alt-Svc/HTTPS-record advertisement and verify the response actually used H3. For independent public analysis, try tls3.peet.ws/api/all, but the URL alone does not guarantee QUIC, check actual http_version. In pcap compare TLS, complete transport-parameter payloads, first Initial size, CID, SETTINGS and header order. After decryption, save SETTINGS type4 before converting to a map: compare ID/value order and GREASE separately. For HEADERS type1, parse the QPACK prefix and instructions first, then compare decoded values. For equal values, compare static indices, literal/name-reference and Huffman/Never Index. Test QPACK dynamics separately with nonzero server capacity, multiple requests and encoder/decoder streams. Do not automatically apply TCP GREASE rules to QUIC.
  7. Measure HTTP/3 behavior per connection. Send four safe GETs using one client, separated by2,42 and 2 seconds. On your server save connection ID, protocol, stream ID and each request’s time. Repeat with capacity 0/65536 and 0-RTT allowed/disallowed. To reject early data, retain ticket keys and PSK but prohibit early data, then verify delivery exactly once after the handshake. Separately record early_data offer, server acceptance and HTTP-stream opening in 0-RTT. Public /client gives brief detection, this timing sequence needs pcap with session keys or your HTTP/3 server logs.

curl_cffi example: the first URL provides detailed fields, the second brief detection. We use available chrome150 here, the name chrome does not promise154 support.

from curl_cffi import requests

with requests.Session(impersonate="chrome150") as session:

    for url in ("https://tls.peet.ws/api/all",

                "https://audit.blanktrail.com/client"):

        response = session.get(url, timeout=20)

        print(response.json())

For an old client through BlankTrail, configure a port with the required profile and appropriate CA, compare H2 and H3 separately. Example:

curl --proxy http://127.0.0.1:PROXY_PORT \

  --cacert /path/to/blanktrail-ca.crt \

  https://tls.peet.ws/api/all
Static view of anonymized real detector responses: requests, wreq and BlankTrail. These responses are from TCP checks.
Static view of anonymized real detector responses: requests, wreq and BlankTrail. These responses are from TCP checks.

The illustration preserves real brief /client responses from TCP checks, without IPs or one-time names. Their green result applies to TCP and does not confirm HTTP/3 matching.

Which solutions came out ahead

BlankTrail ranks first for tested coverage of current targets and ready-made profiles. Among libraries, httpcloak is closest to Chrome QUIC in the raw fields checked, but fails after idle and on 0-RTT rejection. Go tls-client and noble-tls accurately reproduced tested Chrome TCP/H2, but have QUIC/QPACK limitations, noble-tls also duplicated a GET. In wreq/primp, matching JA4 hides empty trust_anchors. Other clients’ older presets retain the listed TLS/H2/H3 defects.

Ranks consider target-profile accuracy, browser coverage, HTTP/3 and maintenance convenience. This ranks tested configurations, not antifraud detection probability.

Limits of result confirmation

Confirmation limits and iOS classification

For current Firefox 156, the saved raw TCP reference is fresh. On resumption the product adds41 and removes35, but there is no full paired capture of resumed Firefox 156 yet: the transition is neither called a proven defect nor presented as a full raw reference comparison. Safari TCP confirms ciphers, extensions, signature algorithms, H2 and no PSK, but not every extension payload has a saved paired browser reference. Edge accepted QUIC0-RTT in all four qz repeats, with early HTTP in two.0-RTT acceptance does not mean every HTTP request precedes handshake completion, comparing frequencies needs an equivalent real-Edge series.

Ranking all 20 clients

Priority: Chrome 154 accuracy, then Safari/Firefox/Edge/Android and HTTP/3, then ready-made profiles and integration. ✓ - criterion confirmed, △ - partial or limited, ✕ - absent capability or unmet target. Explanations are in tooltips and client cards. Green Chrome refers to TCP/H2, HTTP/3 is assessed separately.

Fingerprint test results

№ClientChrome154Safari26Firefox156Edge154Android154HTTP/3HTTP/2trust_anchorsClient HintsGREASERepeat TLS
1BlankTrail Proxy✓✓✓✓✓✓✓✓✓✓✓
2httpcloak✓△✕✕△△△✓△△✓
3Go tls-client✓△✕✕△✕△✓△△△
4noble-tls✓△✕✕△✕△✓△△△
5wreq Python△△✕✕△✕✓✕✕△✓
6primp△△✕✕△✕✓✕✕✕✕
7wreq-js✕△✕✕✕✕✓✕✕△△
8curl_cffi / curl-impersonate✕✕✕✕✕✕✓✕△✕✕
9rnet✕△✕✕✕✕✓✕✕✕△
10Python tls-client✕✕✕✕✕✕✓✕△✕△
11req/v3✕✕✕✕✕✕△✕△✕✕
12uTLS + Go HTTP/2✕✕✕✕✕✕✕✓△△△
13impit✕✕✕✕✕✕✕✕△✕✕
14CycleTLS✕△✕✕✕✕△✕△✕△
15got-scraping✕✕✕✕✕✕✕✕✓✕✕
16HTTPX✕✕✕✕✕✕△✕△✕✕
17Go net/http✕✕✕✕✕✕△✕△✕△
18Windows curl✕✕✕✕✕✕✕✕△✕✕
19requests✕✕✕✕✕✕✕✕△✕✕
20Node fetch✕✕✕✕✕✕✕✕△✕△

Integration, profiles and maintenance

№ClientIntegrationProfilesUpdatesStack
1BlankTrail Proxy✓✓✓✓
2httpcloak△△△△
3Go tls-client△△△△
4noble-tls✓△△△
5wreq Python✓✕△△
6primp✓✕△△
7wreq-js✓✕△△
8curl_cffi / curl-impersonate✓✕△△
9rnet✓✕△△
10Python tls-client✓✕△△
11req/v3△△△△
12uTLS + Go HTTP/2✕△✕△
13impit✓✕△△
14CycleTLS△△✕△
15got-scraping✓✕✕△
16HTTPX✓✕✕△
17Go net/http✓✕✕△
18Windows curl✓✕✕△
19requests✓✕✕△
20Node fetch✓✕✕△

Strengths, limitations and verdict for each client

Expand a card for versions, selected profiles and explanations of every rating. Client order and ranks are identical in both tables.

1. BlankTrail Proxy 1.3.15

Strengths: Ready-made coverage of current targets, connects legacy/third-party software through a proxy without code changes, tested Android H3 and Chromium QPACK fields match references

Weaknesses / limitations: Standalone application: requires a separate installation, offset by cross-platform support and Docker deployment.

Verdict: First for ready-made coverage of current targets and proxy compatibility with third-party software. Tested H3/QPACK fields match references, separate installation and stated confirmation limits remain. HTTP/3:72 error-free requests,36 connections with no differences in checked fields, early GET and 0-RTT rejection tested.

✓ Chrome154
H2:154 fields
✓ Safari26 mac/iOS
TCP Safari 26 and database/UA CriOS/FxiOS/EdgiOS: WebKit H2,24 iOS connections without PSK
✓ Firefox156
Fresh Firefox 156 TCP/H2 and checked H3 fields matched, no full paired raw resumed TCP reference 156
✓ Edge154
Edge 154 TCP/H2 and checked H3 fields, including Chromium encoder stream 10, matched
✓ Chrome Android154
New physical Android 154: checked TLS/TP/SETTINGS/Initial and QPACK stream 10 matched, PSK and early qz request confirmed
✓ HTTP/3
342 QUIC connections across six profiles with no differences in the listed stable fields,300 fresh for GREASE/QPACK,42 for fresh/resumed, not universal byte equality
✓ HTTP/2
Target profiles
✓ trust_anchors
Chrome: ID set
✓ Client Hints
Desktop Client Hints aligned, iOS WebKit without desktop CH. Requiring CH from CriOS/EdgiOS in current /client is a detector-classification error
✓ GREASE / shuffling
Structure, variability and shuffling confirmed by the series, no universal statistical equivalence claimed
✓ Resumption
Safari TCP without PSK, H3 PSK and early HTTP confirmed for all six profiles. Edge: QUIC0-RTT4/4, early HTTP2/4, no full resumed TCP reference for Firefox 156
✓ Integration
Connect through HTTP/SOCKS proxy without embedding a library in application source. The corresponding HTTPS mode requires CA trust configuration.
✓ Required profiles
Ready-made154/156/26
✓ Keeping profiles current
Product database, within five days of release
✓ Code / stack
Works through a proxy with different stacks, legacy and third-party software without source access. Compatibility depends on the required proxy mode and CA trust.
2. httpcloak1.7.2

Strengths: Best confirmed Chrome QUIC among libraries in these tests

Weaknesses / limitations: No exact set of all current targets, custom maintenance. H3: auto stayed on TCP, errors after q/qd idle and on 0-RTT rejection, retry=2 did not help.

Verdict: Above Go/noble for Chrome H3, below BlankTrail for ready-made coverage

✓ Chrome154
custom154/H2
△ Safari26 mac/iOS
iOS: TLS summary, different H2, no Mac preset
✕ Firefox156
Latest is not156, H3 differs
✕ Edge154
Chrome with Edge UA: extra51764
△ Chrome Android154
H2 latest after CH154
△ HTTP/3
Chrome: checked fields matched, FF/Safari differ
△ HTTP/2
Chrome yes, Safari no
✓ trust_anchors
Chrome: ID set
△ Client Hints
Aligned in custom, other targets manual
△ GREASE / shuffling
Chrome: observed, not all targets
✓ Resumption
Chrome PSK accepted, Safari repeats without PSK
△ Integration
JSON configuration and API
△ Required profiles
154 manual, latest152
△ Keeping profiles current
Project updates presets, custom profiles are your responsibility
△ Code / stack
Several SDKs, integration required
3. Go tls-client1.16.0

Strengths: TLS/H2 constructors, accurate tested Chrome H2

Weaknesses / limitations: Mac Safari 16, current Edge/Firefox, QUIC/SETTINGS, QPACK Huffman for ?0. H3: resumes on second request, no early GET,0-RTT rejection handled.

Verdict: Strong H2 constructor, H3 lowers the overall result

✓ Chrome154
152_PSK:154 H2 fields
△ Safari26 mac/iOS
iOS26 fresh H2, mac16 outdated
✕ Firefox156
148 is not156
✕ Edge154
Chrome profile is not Edge 154
△ Chrome Android154
152_PSK/H2 with Android CH154
✕ HTTP/3
QUIC TLS/CID,152_PSK SETTINGS only 51, QPACK ?0 encoded differently
△ HTTP/2
Chrome/iOS yes, mac16 no
✓ trust_anchors
Chrome H2: ID set
△ Client Hints
Set by calling code
△ GREASE / shuffling
Chrome TCP yes, QUIC differs
△ Resumption
Chrome yes, iOS26 without PSK, mac16 outdated
△ Integration
Go API, code required
△ Required profiles
No154, profile/headers manual
△ Keeping profiles current
Core releases + custom maintenance
△ Code / stack
Go, other languages through wrappers
4. noble-tls0.1.9 / native1.16.0

Strengths: Convenient async with Go tls-client core

Weaknesses / limitations: H3 differences, separate wrapper/core versions. H3: first GET duplicated over H2/H3 in protocol racing, no early GET,0-RTT rejection handled.

Verdict: More maintenance than the direct Go API

✓ Chrome154
152_PSK:154 H2 fields
△ Safari26 mac/iOS
iOS26/H2, mac16 differs
✕ Firefox156
148 is not156
✕ Edge154
Chrome profile is not Edge 154
△ Chrome Android154
152_PSK/H2 with CH154
✕ HTTP/3
QUIC differs, wrapper raw QPACK not captured separately
△ HTTP/2
Chrome/iOS yes
✓ trust_anchors
Chrome H2: ID set
△ Client Hints
Set by Python code
△ GREASE / shuffling
TCP yes, QUIC differs
△ Resumption
Chrome yes, iOS26 without PSK, mac16 outdated
✓ Integration
Python async API
△ Required profiles
New names through native core, enum lags behind
△ Keeping profiles current
Update package and native core separately
△ Code / stack
Python + native binaries
5. wreq Python0.12.3

Strengths: Good Safari H2 presets, platform selection

Weaknesses / limitations: Empty trust anchors, old CH, H3 did not work

Verdict: Above primp for checked Chrome TLS fields

△ Chrome154
153: JA4/H2 yes, raw defects
△ Safari26 mac/iOS
26 mac/iOS H2 summaries
✕ Firefox156
151 is not156
✕ Edge154
148 is not154
△ Chrome Android154
153: H2 summaries
✕ HTTP/3
HTTP_3: NO_APPLICATION_PROTOCOL
✓ HTTP/2
Chrome/Safari summaries
✕ trust_anchors
Empty51764
✕ Client Hints
153 with UA154
△ GREASE / shuffling
sigGREASE/shuffling, incomplete raw coverage of all targets
✓ Resumption
Chrome PSK, Safari 26 repeats without PSK
✓ Integration
Python sync/async
✕ Required profiles
No ready-made154
△ Keeping profiles current
wreq releases, update dependency
△ Code / stack
Python/Rust, separate Node package
6. primp2.0.1 (Python)

Strengths: Simple API, broad preset selection

Weaknesses / limitations: trust anchors/ALPS/GREASE, different Safari PSK, H2-closure errors

Verdict: Convenience does not compensate for raw differences

△ Chrome154
153: summaries match, raw differences
△ Safari26 mac/iOS
26: summaries match, raw differences
✕ Firefox156
151 is not156
✕ Edge154
151: raw signatures/ALPS differ
△ Chrome Android154
153: H2 summaries
✕ HTTP/3
The tested Python primp2.0.1 package was built without the http3 feature and offers no QUIC/H3 API. The Rust core has a separate optional http3 feature, but that is another build, whose results cannot be applied to the Python package.
✓ HTTP/2
Summaries obtained
✕ trust_anchors
Empty51764
✕ Client Hints
153 with UA154
✕ GREASE / shuffling
No GREASE in Chrome sigalgs
✕ Resumption
Safari 26: PSK1 accepted,2/4 responses lost
✓ Integration
Python sync/async
✕ Required profiles
No154
△ Keeping profiles current
Package releases, limited custom configuration
△ Code / stack
Python/Rust transport
7. wreq-js3.2.0

Strengths: Safari H2 and familiar fetch

Weaknesses / limitations: Old Chrome/Edge, exact154 not obtained

Verdict: Below newer Python presets

✕ Chrome154
149: different TLS
△ Safari26 mac/iOS
26 mac/iOS H2 summaries
✕ Firefox156
151 is not156
✕ Edge154
148 is not154
✕ Chrome Android154
149: different TLS
✕ HTTP/3
wreq-js3.2.0 and its wreq0.16.1 have no supported QUIC/H3 transport. HTTP3 ALPN in TLS configuration types does not create a QUIC transport, auto requests used TCP/H2.
✓ HTTP/2
Chrome/Safari summaries
✕ trust_anchors
Required payload missing
✕ Client Hints
Old preset, manual setup
△ GREASE / shuffling
Overrides available, current154 not obtained
△ Resumption
Safari 26 without PSK, exact Chrome 154 not obtained
✓ Integration
Fetch API Node/Bun
✕ Required profiles
154 rejected
△ Keeping profiles current
npm/core releases
△ Code / stack
Node/Bun, code integration
8. curl_cffi0.16.3 / curl-impersonate

Strengths: Broad API, working H3, low integration cost

Weaknesses / limitations: Current targets mismatch at TLS/QUIC. H3 PSK works after idle, but the tested profile did not offer early data.

Verdict: H3 and flexibility are useful, exact154 not obtained

✕ Chrome154
150: no 51764/sigGREASE
✕ Safari26 mac/iOS
Safari 260: raw/QUIC differences
✕ Firefox156
147: not156, old resumed defect
✕ Edge154
Edge 101 outdated
✕ Chrome Android154
Android 131 outdated
✕ HTTP/3
Chrome SETTINGS matched, TLS/Initial 154 and Safari raw fields differ
✓ HTTP/2
Chrome H2 matched
✕ trust_anchors
No51764
△ Client Hints
Manual headers / presets
✕ GREASE / shuffling
No target 154 sigGREASE
✕ Resumption
Safari:3 PSKs accepted, Firefox resumed retains35
✓ Integration
Requests API sync/async
✕ Required profiles
No154, custom attempt did not match
△ Keeping profiles current
Library releases, maintain custom yourself
△ Code / stack
Python/libcurl/CLI ecosystem
9. rnet2.4.2

Strengths: Ready-made presets and sync/async

Weaknesses / limitations: Old catalog, H3 API rejected request

Verdict: Profile availability limits the result

✕ Chrome154
137 outdated
△ Safari26 mac/iOS
18.5:26 summary, raw differences
✕ Firefox156
139 is not156
✕ Edge154
134 is not154
✕ Chrome Android154
137 with Android UA differs
✕ HTTP/3
HTTP_3: UnsupportedVersion
✓ HTTP/2
Chrome/Safari summaries
✕ trust_anchors
No51764
✕ Client Hints
Old preset CH
✕ GREASE / shuffling
Old sigalgs/ALPS
△ Resumption
Safari repeats without PSK, full current profile unconfirmed
✓ Integration
Python sync/async
✕ Required profiles
No154
△ Keeping profiles current
Package updates
△ Code / stack
Python/Rust
10. Python tls-client1.0.1

Strengths: Easy to get started

Weaknesses / limitations: Core and catalog significantly behind

Verdict: Convenient API with old transport

✕ Chrome154
120 outdated
✕ Safari26 mac/iOS
Safari 15.6 outdated
✕ Firefox156
Four requests with firefox_120 and Firefox 156 UA. Raw groups:29,23,24,25,256,257, reference 156 has4588 and a different set. No ready-made156 in catalog 1.0.1.
✕ Edge154
No ready-made Edge 154 in catalog 1.0.1. Tested Chromium chrome_120 with Edge 154 UA: groups lack4588, old ALPS17513, no reference GREASE in signature_algorithms.
✕ Chrome Android154
120 with Android UA differs
✕ HTTP/3
Installed Python wrapper1.0.1 and its bundled native transport have no supported HTTP/3 API, no QUIC connections obtained.
✓ HTTP/2
Chrome H2 summary
✕ trust_anchors
No51764
△ Client Hints
Headers set manually
✕ GREASE / shuffling
Old groups/ALPS
△ Resumption
Safari 15.6 without PSK, but fresh TLS outdated
✓ Integration
Python, requests-like
✕ Required profiles
154 name does not produce a correct profile
△ Keeping profiles current
Native core and package need updating
△ Code / stack
Python/native
Details of supported configuration tested

Python tls-client1.0.1: firefox_120 with UA156 retained old groups without 4588, the Chromium profile with Edge 154 UA retained old ALPS and signature algorithms. No ready-made target versions. req/v3: ImpersonateSafari and ImpersonateFirefox use old Safari 16/Firefox 120 profiles, Chrome preset is120. uTLS1.8.2: Auto selects Chrome 133, Firefox 120, Safari 16 and Edge 85. Their new TCP connections did not match current targets. Custom ClientHelloSpec can build another configuration, but does not make these ready-made profiles current. Versions and presets can be checked in API req/v3 and API uTLS1.8.2.

Details of supported configuration tested

HTTP/3 is assessed for the specific distribution. Tested Python primp2.0.1 has no QUIC: its Python build omits the Rust core’s optional http3 feature. In wreq-js3.2.0, Python tls-client1.0.1, got-scraping4.2.1 and ordinary control transports, HTTP/3 is absent. uTLS + Go HTTP/2 also has no QUIC, adding a separate QUIC client would test a different solution.

11. req/v3 3.61.0

Strengths: Flexible TLS/H2 API and working H3

Weaknesses / limitations: Built-in Chrome is not the current target. H3 connection survived idle, a new handshake after server closure had no PSK/0-RTT.

Verdict: Above low-level uTLS for ready-made HTTP3 API

✕ Chrome154
ImpersonateChrome differs
✕ Safari26 mac/iOS
Four new TCP connections tested: Safari 16 with target-version UA. No ready-made current profile, stable TLS fields differ from the reference. For uTLS, HTTP/2 remains a separate transport. Results apply to these presets and do not deny the ability to build custom ClientHelloSpec.
✕ Firefox156
Four new TCP connections tested: Firefox 120 with target-version UA. No ready-made current profile, stable TLS fields differ from the reference. For uTLS, HTTP/2 remains a separate transport. Results apply to these presets and do not deny the ability to build custom ClientHelloSpec.
✕ Edge154
Four new TCP connections tested: Chrome 120 with target-version UA. No ready-made current profile, stable TLS fields differ from the reference. For uTLS, HTTP/2 remains a separate transport. Results apply to these presets and do not deny the ability to build custom ClientHelloSpec.
✕ Chrome Android154
Four new TCP connections tested: Chrome 120 with target-version UA. No ready-made current profile, stable TLS fields differ from the reference. For uTLS, HTTP/2 remains a separate transport. Results apply to these presets and do not deny the ability to build custom ClientHelloSpec.
✕ HTTP/3
H3 works, raw fields/SETTINGS differ
△ HTTP/2
Configuration possible, target not matched
✕ trust_anchors
Required51764 absent
△ Client Hints
Manual headers
✕ GREASE / shuffling
Tested154 not reproduced
✕ Resumption
Four new TCP sessions each for ImpersonateChrome/Safari/Firefox: no pre_shared_key. Chromium/Firefox resumed-fingerprint transitions not reproduced, old Safari profile does not confirm current Safari as a whole.
△ Integration
Go API / low-level options
△ Required profiles
Assemble the required profile
△ Keeping profiles current
Partial database, custom maintained yourself
△ Code / stack
Go
12. uTLS + Go HTTP2

Strengths: Maximum ClientHelloSpec freedom

Weaknesses / limitations: TLS alone does not fix HTTP2/3

Verdict: A tool for building a client, not a ready-made target

✕ Chrome154
Ready-made preset is not154
✕ Safari26 mac/iOS
Four new TCP connections tested: Safari 16 with target-version UA. No ready-made current profile, stable TLS fields differ from the reference. For uTLS, HTTP/2 remains a separate transport. Results apply to these presets and do not deny the ability to build custom ClientHelloSpec.
✕ Firefox156
Four new TCP connections tested: Firefox 120 with target-version UA. No ready-made current profile, stable TLS fields differ from the reference. For uTLS, HTTP/2 remains a separate transport. Results apply to these presets and do not deny the ability to build custom ClientHelloSpec.
✕ Edge154
Four new TCP connections tested: Edge 85 with target-version UA. No ready-made current profile, stable TLS fields differ from the reference. For uTLS, HTTP/2 remains a separate transport. Results apply to these presets and do not deny the ability to build custom ClientHelloSpec.
✕ Chrome Android154
Four new TCP connections tested: Chrome 133 with target-version UA. No ready-made current profile, stable TLS fields differ from the reference. For uTLS, HTTP/2 remains a separate transport. Results apply to these presets and do not deny the ability to build custom ClientHelloSpec.
✕ HTTP/3
The tested uTLS + golang.org/x/net/http2 combination has no QUIC/HTTP3 transport. Adding another QUIC library would be a different combination.
✕ HTTP/2
Standard Go H2 differs
✓ trust_anchors
Supported GenericExtension in ClientHelloSpec sent reference trust_anchors51764 payload. Four successful requests, captured extension bytes matched. Manual Spec assembly required, not a ready-made current browser profile. PreferSkipResumptionOnNilExtension=true used for the Spec without PSK.
△ Client Hints
Manual
△ GREASE / shuffling
Flexible Spec, setup required
△ Resumption
Cache/Spec managed by developer
✕ Integration
Low-level integration
△ Required profiles
Build TLS and H2 separately
✕ Keeping profiles current
Maintain custom profiles yourself
△ Code / stack
Go, custom transport
Details of supported configuration tested

Low-level capabilities are assessed separately: uTLS’s supported GenericExtension in ClientHelloSpec sent reference trust_anchors51764. Four successful requests and raw ClientHello confirmed matching bytes for this extension, earning green in that column. Manual Spec assembly and separate H2 transport remain necessary, matching one extension does not turn ready-made Chrome 133 into current Chrome 154.

13. impit0.14.5

Strengths: Simple API and working H3

Weaknesses / limitations: No exact current Chrome/Safari, H2/QUIC defects. H3 PSK works after idle, but no early HTTP.

Verdict: H3 support did not improve impersonation accuracy

✕ Chrome154
151: different TLS/H2
✕ Safari26 mac/iOS
No Safari in tested enum
✕ Firefox156
144 is not156
✕ Edge154
Chrome with Edge UA is not Edge
✕ Chrome Android154
Android: different TLS/H2
✕ HTTP/3
H3 works, raw fields differ greatly
✕ HTTP/2
Different SETTINGS/window
✕ trust_anchors
Required51764 absent
△ Client Hints
Headers need alignment
✕ GREASE / shuffling
QUIC retains a TCP-like set
✕ Resumption
Repeat TLS differs
✓ Integration
Fetch Python/Node
✕ Required profiles
154 rejected
△ Keeping profiles current
Project releases
△ Code / stack
Python/Node/native
14. CycleTLS2.0.5

Strengths: Supported JA3/JA4R/H2 parameters, working H3, manually matched Firefox H2 summary

Weaknesses / limitations: Chrome 154 JA3 rejects extension 51764, JA4R changes groups, JA3/combined settings select TLS1.2. Safari recognized by brief detection but raw fields differ. Forced H3 uses a separate fresh connection for every GET without PSK/0-RTT.

Verdict: Flexible constructor, but tested settings did not reproduce full current targets, limited Safari/H2 success shown in yellow

✕ Chrome154
JA3/H2 custom154: handshake error
△ Safari26 mac/iOS
Supported manual JA4R produced four successful TLS1.3 requests, brief detection recognized Safari 26. Raw supported_groups were29,29,23,24 rather than a set including4588, no exact Safari match. JA3 and combined JA3+JA4R selected TLS1.2.
✕ Firefox156
JA3, JA4R and their combination were tested with reference Firefox 156 HTTP2Fingerprint. JA3/combined mode gave TLS1.2 instead of TLS1.3, JA4R changed groups and failed the handshake. Full156 fingerprint not obtained.
✕ Edge154
JA3/JA4R and combined Edge 154 configuration tested. JA3/combined gave TLS1.2 and different signature_algorithms, JA4R a handshake error. H2 also has extra/different SETTINGS.
✕ Chrome Android154
Tested manual Chrome 154 configuration applied to Android target rejected extension 51764 (trust_anchors). Separate JA4R also failed to handshake. Target not achieved with tested settings.
✕ HTTP/3
H3 works, JA3 did not produce required QUIC
△ HTTP/2
Manually supplied Firefox HTTP2Fingerprint matched H2 summary:1:65536,4:131072,5:16384|12517377|0|m,p,a,s. Chrome/Edge/Safari configurations did not fully reproduce SETTINGS, no complete H2 pass across all targets.
✕ trust_anchors
Extension51764 error in H2
△ Client Hints
Headers configured manually
✕ GREASE / shuffling
Different QUIC set/groups
△ Resumption
Successful Safari JA4R run had four fresh TLS1.3 Hellos without PSK, consistent with no Safari TCP resumption, but raw fields differed. Current Chromium/Firefox PSK transitions not obtained.
△ Integration
Node + native / explicit parameters
△ Required profiles
JA3/JA4/H2 configuration, not ready-made154
✕ Keeping profiles current
Custom maintenance yourself
△ Code / stack
Node/native, integration required
Details of supported configuration tested

CycleTLS2.0.5: three supported configurations tested: JA3 with reference H2 summary, JA4R with signature algorithms, and combined JA3+JA4R. Chrome 154 JA3 rejects extension 51764. JA4R alone sets different groups, Chrome/Edge/Firefox handshakes failed. Safari JA4R produced four successful TLS1.3 requests and brief Safari 26 recognition, but raw groups29,29,23,24 differ from the reference with 4588. JA3 and combined settings chose TLS1.2. Manually supplied Firefox H2 summary matched, so that result counts as partial. Settings are available in the supported CycleTLS API, a particularly clear example of the difference between a brief summary and the full fingerprint.

15. got-scraping4.2.1

Strengths: Convenient header generator

Weaknesses / limitations: EOL and nonbrowser transport

Verdict: Headers do not compensate for TLS/H2

✕ Chrome154
Headers154, different TLS/H2
✕ Safari26 mac/iOS
Four requests with Safari 26.6.2 headers. The generator changes headers and some TLS options, but raw TLS fields and H2 do not match the browser. H2 summary throughout:2:0,4:33554432|4128769|0|m,p,a,s.
✕ Firefox156
Four requests with Firefox 156 headers. The generator changes headers and some TLS options, but raw TLS fields and H2 do not match the browser. H2 summary throughout:2:0,4:33554432|4128769|0|m,p,a,s.
✕ Edge154
Four requests with Edge 154 headers. The generator changes headers and some TLS options, but raw TLS fields and H2 do not match the browser. H2 summary throughout:2:0,4:33554432|4128769|0|m,p,a,s.
✕ Chrome Android154
Four requests with Chrome Android 154 headers. The generator changes headers and some TLS options, but raw TLS fields and H2 do not match the browser. H2 summary throughout:2:0,4:33554432|4128769|0|m,p,a,s.
✕ HTTP/3
got-scraping4.2.1 uses Node TLS and http2-wrapper, no QUIC/HTTP3 API. Auto run used TCP/H2.
✕ HTTP/2
Not Chrome
✕ trust_anchors
Required51764 absent
✓ Client Hints
Headers154 generator
✕ GREASE / shuffling
Transport is not Chrome
✕ Resumption
Repeat TCP measurements showed no pre_shared_key. Changing browser headers did not reproduce Chromium or Firefox fresh/resumed fingerprints. Results apply to the tested got-scraping configuration.
✓ Integration
Got/Node API
✕ Required profiles
No154 transport
✕ Keeping profiles current
EOL, no current maintenance
△ Code / stack
Node
16. HTTPX0.28.1

Strengths: Reliable, familiar HTTP API

Weaknesses / limitations: Changing UA does not replace TLS/H2/QUIC

Verdict: Control client, not an impersonation solution

✕ Chrome154
Control: not Chrome
✕ Safari26 mac/iOS
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Firefox156
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Edge154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Chrome Android154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ HTTP/3
HTTP/3 absent from the tested supported transport, no browser QUIC fingerprint obtained.
△ HTTP/2
HTTP2 enabled, different
✕ trust_anchors
Captured ClientHello lacks trust_anchors with the reference Chrome 154 ID set.
△ Client Hints
Can be set manually
✕ GREASE / shuffling
Captured ClientHello lacks browser GREASE and corresponding Chromium extension shuffling.
✕ Resumption
No Chrome/Firefox PSK transition after reconnecting in the tested configuration. curl used four requests in one process, Python retained its client. This describes that configuration, not the absence of all TLS-session support in the backend.
✓ Integration
Python sync/async
✕ Required profiles
No built-in browser TLS/H2/QUIC profile database, a User-Agent header is not a profile.
✕ Keeping profiles current
No browser-fingerprint database. Updating the HTTP client does not provide one.
△ Code / stack
Python
17. Go net/http

Strengths: Reliable, familiar HTTP API

Weaknesses / limitations: Changing UA does not replace TLS/H2/QUIC

Verdict: Control client, not an impersonation solution

✕ Chrome154
Control: not Chrome
✕ Safari26 mac/iOS
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Firefox156
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Edge154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Chrome Android154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ HTTP/3
HTTP/3 absent from the tested supported transport, no browser QUIC fingerprint obtained.
△ HTTP/2
HTTP2 supported, different
✕ trust_anchors
Captured ClientHello lacks trust_anchors with the reference Chrome 154 ID set.
△ Client Hints
Can be set manually
✕ GREASE / shuffling
Captured ClientHello lacks browser GREASE and corresponding Chromium extension shuffling.
△ Resumption
Server-accepted TLS1.3 PSK resumption confirmed. Repeats retain native Go/Node fingerprints, without emulating target-browser transitions.
✓ Integration
Standard Go API
✕ Required profiles
No built-in browser TLS/H2/QUIC profile database, a User-Agent header is not a profile.
✕ Keeping profiles current
No browser-fingerprint database. Updating the HTTP client does not provide one.
△ Code / stack
Go
Details of supported configuration tested

Repeat TLS: Go net/http with ClientSessionCache enabled and built-in Node fetch obtained server-accepted PSK but retained nonbrowser ClientHello. This is partial transport capability, not confirmed resumed-browser emulation. HTTPX, requests, Windows curl and ready-made req/v3 profiles did not produce required Chromium/Firefox PSK transitions in tested settings. The assessment describes reproducible results for those configurations, not an inherent inability of their TLS backend to store sessions. For Safari, no TCP PSK is compared against Safari references and is not penalized by itself.

18. Windows curl / Schannel

Strengths: Reliable, familiar HTTP API

Weaknesses / limitations: Changing UA does not replace TLS/H2/QUIC

Verdict: Control client, not an impersonation solution

✕ Chrome154
Control: not Chrome
✕ Safari26 mac/iOS
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Firefox156
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Edge154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Chrome Android154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ HTTP/3
HTTP/3 absent from the tested supported transport, no browser QUIC fingerprint obtained.
✕ HTTP/2
H2 depends on build, measured transport differs
✕ trust_anchors
Captured ClientHello lacks trust_anchors with the reference Chrome 154 ID set.
△ Client Hints
Can be set manually
✕ GREASE / shuffling
Captured ClientHello lacks browser GREASE and corresponding Chromium extension shuffling.
✕ Resumption
No Chrome/Firefox PSK transition after reconnecting in the tested configuration. curl used four requests in one process, Python retained its client. This describes that configuration, not the absence of all TLS-session support in the backend.
✓ Integration
CLI/proxy
✕ Required profiles
No built-in browser TLS/H2/QUIC profile database, a User-Agent header is not a profile.
✕ Keeping profiles current
No browser-fingerprint database. Updating the HTTP client does not provide one.
△ Code / stack
CLI across stacks
19. requests2.32.5

Strengths: Reliable, familiar HTTP API

Weaknesses / limitations: Changing UA does not replace TLS/H2/QUIC

Verdict: Control client, not an impersonation solution

✕ Chrome154
Control: not Chrome
✕ Safari26 mac/iOS
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Firefox156
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Edge154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Chrome Android154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ HTTP/3
HTTP/3 absent from the tested supported transport, no browser QUIC fingerprint obtained.
✕ HTTP/2
H1, no H2 in this transport
✕ trust_anchors
Captured ClientHello lacks trust_anchors with the reference Chrome 154 ID set.
△ Client Hints
Can be set manually
✕ GREASE / shuffling
Captured ClientHello lacks browser GREASE and corresponding Chromium extension shuffling.
✕ Resumption
No Chrome/Firefox PSK transition after reconnecting in the tested configuration. curl used four requests in one process, Python retained its client. This describes that configuration, not the absence of all TLS-session support in the backend.
✓ Integration
Python sync
✕ Required profiles
No built-in browser TLS/H2/QUIC profile database, a User-Agent header is not a profile.
✕ Keeping profiles current
No browser-fingerprint database. Updating the HTTP client does not provide one.
△ Code / stack
Python
20. Node fetch

Strengths: Reliable, familiar HTTP API

Weaknesses / limitations: Changing UA does not replace TLS/H2/QUIC

Verdict: Control client, not an impersonation solution

✕ Chrome154
Control: not Chrome
✕ Safari26 mac/iOS
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Firefox156
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Edge154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ Chrome Android154
No browser profiles. Requests with this target’s UA retained native TLS fingerprints, changing UA does not reproduce a browser.
✕ HTTP/3
HTTP/3 absent from the tested supported transport, no browser QUIC fingerprint obtained.
✕ HTTP/2
Measured H1
✕ trust_anchors
Captured ClientHello lacks trust_anchors with the reference Chrome 154 ID set.
△ Client Hints
Can be set manually
✕ GREASE / shuffling
Captured ClientHello lacks browser GREASE and corresponding Chromium extension shuffling.
△ Resumption
Server-accepted TLS1.3 PSK resumption confirmed. Repeats retain native Go/Node fingerprints, without emulating target-browser transitions.
✓ Integration
Built-in fetch
✕ Required profiles
No built-in browser TLS/H2/QUIC profile database, a User-Agent header is not a profile.
✕ Keeping profiles current
No browser-fingerprint database. Updating the HTTP client does not provide one.
△ Code / stack
Node

A browser fingerprint combines TLS, HTTP2/3, headers and next-connection behavior. Choose a solution by the tested target and maintenance cost: a browser name in a preset and a changed User-Agent do not replace these measurements.

The entire browser-profile library is available on BlankTrail Proxy’s free Lite plan. You can download the application and test the profiles you need yourself.

Read next