JA4 Fingerprint Decoded: What t13d1516h2 and b0da82dd1658 Mean

b0da82dd1658 is the third section of a Chromium JA4: a resumed TLS 1.3 session with the old ALPS codepoint. Here is how to decode every part of a JA4 string, with real values you can reproduce.

JA4 Fingerprint Decoded: What t13d1516h2 and b0da82dd1658 Mean
In this article

A JA4 fingerprint is a three-part string, a_b_c, computed from a TLS ClientHello. Part a is readable metadata (protocol, TLS version, SNI, cipher and extension counts, ALPN), part b is a 12-character SHA-256 of the sorted cipher list, and part c is a 12-character SHA-256 of the sorted extensions plus signature algorithms. If you searched for b0da82dd1658: it is a c section, and it belongs to Chromium. Specifically, it is what a Chromium-based client produces when it resumes a TLS 1.3 session while still using the older ALPS extension codepoint. The rest of this page shows how we know that, and how to decode any JA4 string yourself.

How a JA4 fingerprint is built: the a_b_c format

The official JA4 technical specification from FoxIO defines the fingerprint as one human-readable prefix and two truncated hashes, joined by underscores. GREASE values are ignored everywhere, and all hashes are lowercase.

SectionCharactersWhat it encodesPossible values
a1Transportt = TLS over TCP, q = QUIC, d = DTLS
a2Highest TLS version (from supported_versions if present, else the ClientHello version)13, 12, 11, 10, s3, d2, 00 = unknown…
a1SNI present?d = domain (SNI sent), i = no SNI (usually connecting to a bare IP)
a2Number of cipher suites (GREASE excluded)00–99
a2Number of extensions (GREASE excluded, SNI and ALPN included)00–99
a2First and last character of the first ALPN valueh2, h1 (http/1.1), h3, 00 = no ALPN
b12SHA-256 of cipher suites as 4-char hex, sorted, comma-joined000000000000 if none
c12SHA-256 of extensions (sorted, SNI 0000 and ALPN 0010 removed) + _ + signature algorithms in wire order000000000000 if none

Two details trip people up. First, the extension count in part a includes SNI and ALPN, but the extension hash in part c excludes them, so the same client produces the same c whether it connects to a hostname or an IP. Second, signature algorithms are hashed in the order the client sent them, not sorted, so a client that reorders its signature preferences changes c even with identical extensions.

Decoding t13d1516h2_8daaf6152771_… piece by piece

  • t: TLS over TCP, not QUIC.
  • 13: the client offers TLS 1.3 as its highest version.
  • d: an SNI extension was present, so the client was connecting to a domain name.
  • 15: 15 cipher suites after removing GREASE.
  • 16: 16 extensions after removing GREASE, counting SNI and ALPN.
  • h2: the first ALPN value is h2, meaning the client prefers HTTP/2.
  • 8daaf6152771: the hash of this sorted cipher string, which you can reproduce with any SHA-256 tool:
printf '%s' "002f,0035,009c,009d,1301,1302,1303,c013,c014,c02b,c02c,c02f,c030,cca8,cca9" \
  | sha256sum | cut -c1-12
# 8daaf6152771

That list is the three TLS 1.3 suites plus Chrome's twelve legacy TLS 1.2 suites. Every Chromium build we checked, from the FoxIO examples through a headless Chrome 148, still produces 8daaf6152771. So the b section tells you "Chromium-family TLS stack" and almost nothing about the version. Version differences show up in c.

One small caveat if you are reading the spec closely: its algorithm summary shows t13d1516h2_8daaf6152771_b186095e22b6, while its worked example hashes to e5627efa2ab1. We recomputed the worked example and got e5627efa2ab1, so treat the first string as illustrative.

What b0da82dd1658 means

b0da82dd1658 is the c section of a Chromium JA4. It is the hash of this exact string:

0005,000a,000b,000d,0012,0017,001b,0023,0029,002b,002d,0033,4469,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601

You can check it yourself:

printf '%s' "0005,000a,000b,000d,0012,0017,001b,0023,0029,002b,002d,0033,4469,fe0d,ff01_0403,0804,0401,0503,0805,0501,0806,0601" \
  | sha256sum | cut -c1-12
# b0da82dd1658

Three extensions in that list identify the client:

  • 0029 (pre_shared_key). This is TLS 1.3 session resumption. It only appears when the client already has a ticket from an earlier connection to the same server. RFC 8446 §4.2.11 defines it. Without it, the same browser hashes to 02713d6af862.
  • 4469 (ALPS, original codepoint). Application-Layer Protocol Settings is a Chrome extension. BoringSSL added a new codepoint for it in September 2023 (0x44cd), and current Chrome sends the new one. So 4469 points to an older Chromium build or a tool that copies one.
  • fe0d (Encrypted Client Hello). Chrome sends a GREASE ECH extension on ordinary connections.

FoxIO's own ja4plus-mapping.csv labels t13d1517h2_8daaf6152771_b0da82dd1658 and t13i1516h2_8daaf6152771_b0da82dd1658 as "Chromium Browser". The prefix arithmetic matches: 15 hashed extensions plus SNI plus ALPN gives 17 with SNI (d), and 16 without it (i). That also exposes an inconsistency you'll see repeated online. A string like t13d1516h2_8daaf6152771_b0da82dd1658 cannot come from a single real ClientHello: with SNI and ALPN both present, those 15 hashed extensions would have to produce a count of 17.

We also reproduced the value directly. On 2026-10-03, curl_cffi 0.16.3 with impersonate="chrome131" sent its first request to tls.peet.ws as t13d1516h2_8daaf6152771_02713d6af862. Its second request on the same session, which resumed the TLS session, came back as t13d1517h2_8daaf6152771_b0da82dd1658. In short, b0da82dd1658 means "a Chromium-style ClientHello, older ALPS codepoint, resumed session". That covers real older Chrome, Edge, Brave and Opera builds, embedded Chromium, and impersonation libraries pinned to a pre-switch Chrome profile. A JA4 alone cannot tell those apart.

Reference table: real JA4 values by client

The values below come from two sources only: FoxIO's published examples and mapping file, and our own captures against tls.peet.ws on 2026-10-03 (versions stated). Library values depend heavily on the TLS backend and its build options, so treat the library rows as examples, not universal constants. We left out rows we could not verify, including a real Firefox and Safari capture of our own.

ClientJA4Source
Headless Chrome 148 (Linux), new connectiont13d1516h2_8daaf6152771_d8a2da3f94cdOur capture, Playwright Chromium
Same browser, resumed sessiont13d1517h2_8daaf6152771_b6f405a00624Our capture
Chromium, older ALPS codepoint, new / resumedt13d1516h2_8daaf6152771_02713d6af862 / t13d1517h2_8daaf6152771_b0da82dd1658FoxIO mapping CSV; reproduced with curl_cffi chrome131
Chrome over QUICq13d0312h3_55b375c5d22e_178839b6cec1FoxIO README
Mozilla Firefox (version not stated)t13d1715h2_5b57614c22b0_7121afd63204FoxIO mapping CSV
Safari (version not stated)t13d2014h2_a09f3c656075_14788d8d241bFoxIO mapping CSV
curl 8.5.0 / OpenSSL 3.0.13 (Ubuntu)t13d3112h2_e8f1e7e78f70_b26ce05bbdd6Our capture; identical on tls.peet.ws, BrowserLeaks and Scrapfly
Python requests 2.31 / urllib3 2.0.7, OpenSSL 3.0.13t13d3112h1_e8f1e7e78f70_b26ce05bbdd6Our capture
Go 1.24.1 net/http (default client)t13d1311h2_f57a46bbacb6_e7c285222651Our capture
curl_cffi 0.16.3, impersonate="chrome146"t13d1516h2_8daaf6152771_d8a2da3f94cdOur capture
curl_cffi 0.16.3, impersonate="firefox147"t13d1717h2_5b57614c22b0_3cbfd9057e0dOur capture
curl_cffi 0.16.3, impersonate="safari2601"t13d2013h2_a09f3c656075_7f0f34a4126dOur capture

The patterns matter more than the individual rows:

  • The c section often fingerprints the TLS library, not the application. curl and Python on the same OpenSSL 3 build share b26ce05bbdd6. FoxIO's mapping file has a Python entry with a different prefix but the same c. Go clients in FoxIO's file share e7c285222651 with our Go 1.24 capture.
  • requests shows h1. urllib3 2.x advertises only http/1.1 in ALPN, so the a section tells a server it is not talking to a browser before any header is read.
  • A plain library is easy to spot by its counts alone. 31 ciphers (OpenSSL default) or 13 ciphers with 11 extensions (Go) looks nothing like the 15/16 pattern every current Chromium build sends.
  • Resumption changes c for every browser. Firefox goes from 17 to 18 extensions and Safari from 13 to 14 on resumed sessions, in our curl_cffi captures. A detection rule that only allow-lists the fresh-connection value will misfire on real returning visitors.

Expect the Chrome row to keep moving. curl_cffi's newest chrome150 profile adds three signature algorithms, 0904, 0905 and 0906. Those are the ML-DSA codepoints from the IETF draft, and they turn c into 806a8c22fdea. We could not confirm that against a stable Chrome 150 build, so treat it as a preview, not a reference value.

Why JA4 sorts ciphers and extensions

JA4 sorts ciphers and extensions because Chrome deliberately randomises its extension order, and an order-sensitive fingerprint (JA3) stopped being stable. Chrome shipped TLS ClientHello extension permutation around Chrome 110 in early 2023. The goal was to stop servers and middleboxes from depending on a fixed layout. Fastly measured the effect: since JA3 hashes extensions in wire order, one Chrome install produces a different JA3 on practically every connection.

JA4 solves this by sorting before hashing. FoxIO published it as part of the JA4+ suite in September 2023 (APNIC republication). It is now exposed by Cloudflare Bot Management, AWS CloudFront and WAF, Google Cloud Armor, Fastly, Akamai and others listed in the FoxIO README.

For scrapers this cuts both ways. Randomising your extension order no longer changes your fingerprint, so that trick is dead. What still matters is the set of ciphers, the set of extensions and the order of signature algorithms. In practice that means using the TLS stack a real browser uses, or a library that copies one closely. If you need the original-order view (for example to compare against JA3), the spec defines JA4_o and the raw variants JA4_r / JA4_ro. BrowserLeaks returns all four. Our TLS fingerprinting explainer covers JA3 in more depth.

The JA4+ family: JA4S, JA4H, JA4X, JA4T

JA4 is one method in a larger suite. The others look at the server, the HTTP layer, certificates and TCP, and they come under a different licence.

MethodFingerprintsFormat, briefly
JA4SServer's ServerHelloprotocol + version + extension count + ALPN, then the chosen cipher, then a hash of the server's extensions
JA4HHTTP requestmethod (ge, po…), version (11, 20), cookie c/n, referer r/n, header count, first 4 chars of Accept-Language; then hashes of header names in order, sorted cookie names, and sorted cookie name=value pairs
JA4XX.509 certificatehashes of issuer RDN OIDs, subject RDN OIDs and extension OIDs (how the cert was built, not its values)
JA4TTCP SYNwindow size, TCP options in order, MSS, window scale, e.g. 64240_2-1-3-1-1-4_1460_8 for Windows 11

Formats are taken from FoxIO's technical details diagrams and the reference Python implementation. JA4H matters most for scraping. Its b section hashes header names in the order sent, so a client that sends Chrome's headers in the wrong order is visible even if every value is right. Our HTTP/2 fingerprinting post covers the related SETTINGS and pseudo-header order signals. JA4T exposes the OS: a Linux server's SYN does not look like Windows, whatever the User-Agent says.

Licensing

According to the repository's licensing section, JA4 (the TLS client fingerprint) is BSD 3-Clause, like JA3, and FoxIO says it has no patent claims on it. JA4S, JA4L, JA4LS, JA4H, JA4X, JA4SSH, JA4T, JA4TS, JA4TScan, JA4D, JA4D6 and the other "JA4+" methods are patent-pending and licensed under the FoxIO License 1.1. That licence allows internal and academic use, but a vendor selling JA4+ fingerprinting as part of a product needs an OEM licence.

QUIC and HTTP/3: what the q prefix tells you

A JA4 starting with q was computed from a QUIC Initial packet, meaning the client was setting up HTTP/3. QUIC carries a TLS 1.3 ClientHello inside its CRYPTO frames. The Initial packet's protection keys are derived from the Destination Connection ID the client chooses (RFC 9001 §5.2), so any on-path observer or edge server can decrypt it and fingerprint the ClientHello exactly as it would over TCP.

Chrome's QUIC fingerprint looks very different from its TCP one: q13d0312h3_55b375c5d22e_178839b6cec1 in FoxIO's example. That is 3 ciphers (TLS 1.3 only, since QUIC forbids older versions) and ALPN h3. This matters for automation because browsers move to HTTP/3 once a site advertises it, while most HTTP clients never do. Cloudflare's documented JA4 Signals include an h2h3_ratio_1h value: the share of traffic for a given JA4 arriving over HTTP/2 versus HTTP/3. A "Chrome" TLS fingerprint that never shows up over QUIC on an HTTP/3 site is a statistical outlier. The curl_cffi README states that HTTP/3 fingerprint impersonation has been supported since v0.15.0.

How to see your own JA4

The fastest check is an echo service that reads your ClientHello and returns the fingerprint. These three returned JA4 in our tests, and they agreed with each other byte for byte:

curl -s https://tls.peet.ws/api/all | python3 -c \
  "import sys, json; d = json.load(sys.stdin); print(d['http_version'], d['tls']['ja4'])"

Run the same check through your proxy, not just from your laptop. A forward proxy that tunnels with CONNECT passes your ClientHello through untouched, but a TLS-intercepting proxy, corporate middlebox or some SDK layers will substitute their own. For example, with a ProxyHat residential exit:

curl -s -x http://USERNAME:PASSWORD@gate.proxyhat.com:8080 https://tls.peet.ws/api/all

Wireshark and tshark

Wireshark has computed JA4 natively since 4.2.0, in the display-filter field tls.handshake.ja4 (and tls.handshake.ja4_r), per the TLS field reference. FoxIO's JA4+ plugin (Wireshark 4.4.0 or later) adds the rest of the suite under ja4.*, such as ja4.ja4s, ja4.ja4h and ja4.ja4t. To pull fingerprints out of a capture:

tshark -r capture.pcapng -Y "tls.handshake.ja4" -T fields \
  -e ip.dst -e tls.handshake.extensions_server_name -e tls.handshake.ja4

Capture on a machine where you control the client. To fingerprint HTTPS traffic from a headless browser, run it locally and capture on the loopback or outbound interface. The ClientHello is unencrypted, so you do not need TLS keys for JA4. You do need them for JA4H on HTTPS.

What this means in practice: headers must match the handshake

Changing the User-Agent does not change your JA4. A Python script that sends a Chrome User-Agent still presents t13d3112h1_e8f1e7e78f70_b26ce05bbdd6 in the handshake, before a single header is read. That mismatch (claims Chrome, negotiates like OpenSSL, asks for HTTP/1.1) is one of the cheapest checks an edge can run. It is also exactly what JA4 databases are built to catch.

There are two mainstream fixes. Both have limits.

  • curl_cffi (Python bindings to curl-impersonate) builds the ClientHello and HTTP/2 frames to match a named browser profile: requests.get(url, impersonate="chrome"). In our captures its current Chrome profiles produced the same JA4 as a real headless Chrome 148. Our curl_cffi guide covers sessions and proxies.
  • uTLS for Go replaces crypto/tls's ClientHello with a "parrot" such as tls.HelloChrome_Auto through tls.UClient(conn, &config, helloID). Its README is explicit that parroting can be imperfect and does not extend beyond the ClientHello. Our Go uTLS walkthrough shows how to wire it into net/http.

What impersonation does not solve

A matching JA4 only gets you past the first filter.

  • The JA4 must agree with the claimed browser version: a Chrome 148 User-Agent with a pre-switch ALPS codepoint (b0da82dd1658) is its own small mismatch.
  • JA4H header order must match too.
  • JA4T and IP data reveal the host OS and network.
  • JavaScript challenges never run in an HTTP client.
  • Behaviour (request rate, navigation pattern) is judged separately.

Pick the profile that matches the User-Agent you send, keep sessions alive so resumption looks normal, and keep the exit IP plausible for the traffic. A residential exit fixes the network-reputation layer, not the TLS layer. Check the fingerprint through the proxy, as shown above.

Finally, the reason to understand fingerprints is to make legitimate automation honest and predictable, not to get past a site that has decided to refuse automated access. Respect robots.txt and terms of service where they apply, and remember that scraping personal data brings legal obligations a perfect JA4 does nothing about.

Frequently asked questions

What is b0da82dd1658 in a JA4 fingerprint?

It is the third (c) section of the JA4: a truncated SHA-256 of the sorted extension list plus signature algorithms. It matches a Chromium-style ClientHello that includes pre_shared_key (a resumed TLS 1.3 session) and the original ALPS codepoint 0x4469. FoxIO's mapping file labels t13d1517h2_8daaf6152771_b0da82dd1658 as Chromium Browser.

What does t13d1516h2 mean in JA4?

t means TLS over TCP, 13 means TLS 1.3, d means an SNI hostname was sent, 15 is the number of cipher suites, 16 is the number of extensions (GREASE excluded, SNI and ALPN included), and h2 is the first and last character of the first ALPN value, i.e. HTTP/2.

What is the difference between JA3 and JA4?

JA3 is one MD5 hash of the ClientHello fields in wire order, so Chrome's extension-order randomisation gives it a new JA3 on almost every connection. JA4 sorts ciphers and extensions before hashing, splits the result into a readable prefix and two hashes, and covers QUIC. JA4 is BSD-licensed like JA3; the wider JA4+ suite is under the FoxIO License.

How can I check my own JA4 fingerprint?

Request tls.peet.ws/api/all, tls.browserleaks.com/json or Scrapfly's JA3/JA4 tool from the client you want to test; each returns the JA4 it saw. For packet captures, Wireshark 4.2+ exposes the tls.handshake.ja4 field, which tshark can print with -T fields.

Does changing the User-Agent change my JA4?

No. JA4 is computed from the TLS ClientHello, which is sent before any HTTP header. A Python or Go client with a Chrome User-Agent still presents its own library's JA4, which is an easy mismatch for a server to spot. Only a different TLS stack, such as curl_cffi or uTLS, changes it.

Test your proxies against real anti-bot defenses

Free proxy checker — latency, anonymity and block signals in one click.

Run a free check
← Back to Blog