Chrome's JA4 changes between versions because Chrome's own TLS stack changes between versions. When our engine was on 153, Google Chrome 152 on the same machine read t13d1517h2, while Google's Chrome for Testing 153 read t13d1518h2: seventeen TLS extensions against eighteen. Neither was wrong. A JA4 compared against a different Chrome major reports that difference as if it were a finding, and it is not one.

What the parts of a JA4 say

A JA4 has three parts separated by underscores. Ours, measured on one profile against Google's Chrome for Testing 153.0.8010.36, was:

t13d1518h2_8daaf6152771_4980c97edce0

The first part is readable. t is TCP, 13 is TLS 1.3, d means the browser sent a domain name in SNI, 15 is the number of cipher suites, 18 the number of extensions, and h2 the first protocol it offered over ALPN. The second part is a truncated hash of the cipher suites, sorted. The third is a hash of the extensions, sorted, together with the signature algorithms.

Sorting is what makes JA4 hold still. Chrome shuffles its extension order on every connection, and JA3, which hashes the order as sent, changes with every shuffle. JA4 sorts first and leaves out the GREASE values Chrome scatters in, so the same binary gives the same JA4 every time. Our post on JA3 has the measurements of that.

Every profile is drawn from one real computer, not from separate listsIn Mosaic: Machines that exist. GPU, screen, CPU and fonts from one real device. Free holds 3 profiles. Pro is $49 a month for any number.

How it works: Machines that exist

Why Chrome's JA4 is different between versions

A sorted list stays the same until the list itself changes, and from one major to the next Chrome changes it. Extensions are added as new TLS features ship, and old ones go. In our run, 153 sent one more extension than 152, so the extension count in the prefix went from 17 to 18. A sorted list with a different set of entries hashes differently too, so the third part moves with it.

That is the browser updating, nothing more. On the same Mac, a stock 153 build sent eighteen extensions and a stock 152 sent seventeen. Both are handshakes real Chrome users send, because people update at different speeds. A site comparing your JA4 against the population of its own visitors sees whichever one matches the version you claim.

Other reasons one Chrome can show two JA4s

Version is the big one, but the prefix has other letters, and each of them follows the connection rather than the browser:

  • Over QUIC, which Chrome uses for HTTP/3, the first letter is q, not t.
  • A connection to a bare IP address sends no domain name, so d becomes i.
  • The last two characters follow what the browser offered over ALPN on that connection.

So before reading two JA4s as two browsers, check they describe the same kind of connection. A TLS echo page reached over TCP and one reached over QUIC can show the same Chrome twice under two prefixes.

Comparing across majors invents findings

This is why our own comparison tool refuses to run across versions. check:stock launches a profile and a stock Chrome side by side and diffs what each exposes. If the two binaries report different Chrome majors, it prints "The profile launched Chromium" with both numbers, says a diff across majors "reports every API added in between as a difference, and none of them would be a leak", compares nothing and exits.

The tempting fix for a red diff is to add the differences to an allow-list until it goes green. That would have made the tool a rubber stamp. Refusing is also the honest prompt to move our patches onto the newer Chrome.

The same discipline cut the other way once. For weeks check:stock compared against Chrome for Testing, Google's unbranded build, and passed. Measured against the Google Chrome people actually install, twenty API entries differed, because an unbranded build runs field trials that branded Chrome does not. The control has to be the browser people run, at the version you claim.

What a JA4 should match

A profile that claims Chrome 153 should send Chrome 153's handshake. Ours did, byte for byte: 15 cipher suites, 18 extensions, ALPN h2, and both hashes equal to stock. Tesseract Chrome is Chromium built from stock source, so it inherits Chrome's TLS stack unchanged, and it has no switch to vary the handshake per profile. Why there is no TLS fingerprint setting gives the reasoning.

What would be a tell is a mismatch inside one browser: a User-Agent claiming one major over a handshake from another. A network-level detector reads the handshake before any JavaScript runs, so that contradiction is the first thing it sees. To compare your own, read the JA4 from a TLS echo service in the browser you are testing and in a real Chrome of the same version, and compare the two JA4 strings, not the JA3s. A web page alone cannot see the raw ClientHello; our TLS fingerprint page shows what a page can read of your connection and explains the rest.