Markdown source

Common JA4 TLS Fingerprints, Decoded Markdown source

Readable source view for humans. The raw Markdown endpoint remains available for crawlers and agent readers.

---
title: "Common JA4 TLS Fingerprints, Decoded"
description: "A lookup table of JA4 fingerprint hashes for Chrome, Firefox, curl, Go, and Python clients, decoded field by field."
kind: article
maturity: seedling
confidence: medium
origin: ai-drafted
author: "Agent"
directedBy: "krow"
tags: [tls, fingerprinting, ja4, bot-detection]
published: 2026-06-13
modified: 2026-06-13
wordCount: 1357
readingTime: 7
related: [ja4-fingerprint-t13d1516h2, ja4-vs-ja3, tls-impersonation-library-comparison]
url: https://krowdev.com/article/common-ja4-fingerprints-decoded/
---
## Agent Context

- Canonical: https://krowdev.com/article/common-ja4-fingerprints-decoded/
- Markdown: https://krowdev.com/article/common-ja4-fingerprints-decoded.md
- Full corpus: https://krowdev.com/llms-full.txt
- Kind: article
- Maturity: seedling
- Confidence: medium
- Origin: ai-drafted
- Author: Agent
- Directed by: krow
- Published: 2026-06-13
- Modified: 2026-06-13
- Words: 1357 (7 min read)
- Tags: tls, fingerprinting, ja4, bot-detection
- Related: ja4-fingerprint-t13d1516h2, ja4-vs-ja3, tls-impersonation-library-comparison
- Content map:
  - h2: Quick Reference
  - h2: How a JA4 string is built
  - h2: Replaying a fingerprint
  - h2: Sources
- Crawl policy: same canonical content is exposed through HTML, Markdown, and llms-full; no crawler-specific content gate.

A JA4 fingerprint like `t13d1516h2_8daaf6152771_<ja4_c>` encodes the TLS ClientHello shape; this page decodes the common Chrome, Firefox, Safari, curl, Go, and Python client strings field by field.

## Quick Reference

Rows marked `live capture 2026-06-13` were observed against `https://tls.peet.ws/api/all` with the named client/profile. Treat them as version/profile observations, not permanent identities.

| Client/profile | JA4 fingerprint | HTTP | Source |
|---|---|---:|---|
| FoxIO canonical JA4 example | `t13d1516h2_8daaf6152771_e5627efa2ab1` | h2 | FoxIO JA4 technical details |
| Published Chrome-family example | `t13d1516h2_8daaf6152771_02713d6af862` | h2 | bunny.net JA4 docs; Google Threat Intelligence deep dive; Cloudflare blog |
| Chrome 131 profile (`curl_cffi chrome131`) | `t13d1516h2_8daaf6152771_02713d6af862` | h2 | live capture 2026-06-13; curl_cffi target docs |
| Chrome 136 profile (`curl_cffi chrome136`) | `t13d1516h2_8daaf6152771_d8a2da3f94cd` | h2 | live capture 2026-06-13; Telegram Desktop issue #30733 |
| Chrome 142 profile (`curl_cffi chrome142`) | `t13d1516h2_8daaf6152771_d8a2da3f94cd` | h2 | live capture 2026-06-13 |
| Chrome Android 131 profile (`curl_cffi chrome131_android`) | `t13d1516h2_8daaf6152771_02713d6af862` | h2 | live capture 2026-06-13; curl_cffi target docs |
| Firefox 133 profile (`curl_cffi firefox133`) | `t13d1716h2_5b57614c22b0_eeeea6562960` | h2 | live capture 2026-06-13; curl_cffi target docs |
| Firefox 135/144 profiles (`curl_cffi firefox135`, `firefox144`) | `t13d1717h2_5b57614c22b0_3cbfd9057e0d` | h2 | live capture 2026-06-13; curl_cffi target docs |
| Safari 18.4 profile (`curl_cffi safari184`) | `t13d2014h2_a09f3c656075_7f0f34a4126d` | h2 | live capture 2026-06-13; curl_cffi target docs |
| Safari 26.0 profile (`curl_cffi safari260`) | `t13d2014h2_a09f3c656075_d0a99439f9b1` | h2 | live capture 2026-06-13; curl_cffi target docs |
| Safari 26.0.1 profile (`curl_cffi safari2601`) | `t13d2013h2_a09f3c656075_7f0f34a4126d` | h2 | live capture 2026-06-13; curl_cffi installed target list |
| curl 8.5.0 + OpenSSL 3.0.13 default | `t13d3112h2_e8f1e7e78f70_375ca2c5e164` | h2 | live capture 2026-06-13 |
| Go `net/http` Go 1.22.2 default | `t13d1411h2_cbb2034c60b8_e7c285222651` | h2 | live capture 2026-06-13 |
| Python `requests` default | `t13d1712h1_ab0a1bf427ad_882d495ac381` | HTTP/1.1 | live capture 2026-06-13 |
| Python `httpx` default | `t13d1712h1_ab0a1bf427ad_8e6e362c5eac` | HTTP/1.1 | live capture 2026-06-13 |
| OpenSSL `s_client -tls1_3` no ALPN | `t13d410_16476d049b0b_78f1d400d464` | HTTP/1.1 | live capture 2026-06-13 |
| OpenSSL `s_client -tls1_3 -alpn h2,http/1.1` | `t13d411h2_16476d049b0b_78f1d400d464` | HTTP/1.1 request after h2 ALPN offer | live capture 2026-06-13 |
| Real Chrome latest desktop | `[varies by version]` | varies | needs JA4DB/export or live real-browser capture |
| Real Firefox latest desktop | `[varies by version]` | varies | needs JA4DB/export or live real-browser capture |
| Real Safari latest desktop | `[varies by version]` | varies | needs JA4DB/export or live real-browser capture |

These hashes are environment- and version-specific: they identify the ClientHello and ALPN shape observed with a profile, library, OS, and build, not an eternal client name. The useful detector signal is the mismatch. A default Python, Go, or curl client emits its own non-browser JA4 even when the User-Agent claims Chrome, so JA4 should be read beside headers, HTTP/2 settings, IP reputation, and behavior as covered in [the bot-detection stack](/article/bot-detection-2026/)
and [HTTP/2 fingerprinting](/article/http2-fingerprinting-akamai/).

Use the table as a lookup index, not a universal allowlist. If a log stores only the prefix, `t13d1516h2` is enough to say “TLS 1.3, SNI, 15 ciphers, 16 extensions, h2,” but it is not enough to distinguish Chrome 131-style captures from newer Chromium-family captures. If the full `a_b_c` value is present, compare all three parts. Rows marked `[varies by version]` are deliberately unpinned because the ref-pack did not contain a verified literal hash for that real-browser/latest claim.

## How a JA4 string is built

JA4 has three parts: `ja4_a`, `ja4_b`, and `ja4_c`, joined with underscores. `ja4_a` is the readable prefix. Its fields are protocol (`t` for TLS over TCP, `q` for QUIC, `d` for DTLS), TLS version, SNI flag (`d` when a domain/SNI is present, `i` when it is absent), two-digit cipher count, two-digit extension count, and the first and last character of the first ALPN value. In `t13d1516h2`, that means TLS over TCP, TLS 1.3, SNI present, 15 ciphers, 16 extensions, and HTTP/2 ALPN. For the deep prefix breakdown, use [the `t13d1516h2` prefix breakdown](/snippet/ja4-fingerprint-t13d1516h2/).

| Segment | Value in `t13d1516h2` | Meaning |
|---|---|---|
| protocol | `t` | TLS over TCP (`q`=QUIC, `d`=DTLS) |
| TLS version | `13` | TLS 1.3 |
| SNI | `d` | SNI present (`i` = absent) |
| cipher count | `15` | 15 ciphers after GREASE removal |
| extension count | `16` | 16 extensions after GREASE removal |
| ALPN | `h2` | first/last char of first ALPN = HTTP/2 |
| `ja4_b` | `_8daaf6152771` | 12 hex of SHA-256 over sorted ciphers |
| `ja4_c` | `_02713d6af862` | 12 hex of SHA-256 over sorted extensions + sigalgs |

`ja4_b` is the cipher hash. FoxIO defines it as the first 12 hexadecimal characters of SHA-256 over comma-delimited cipher hex codes sorted in hex order, with GREASE ignored. That sorting is the practical difference discussed in [JA4 vs JA3](/article/ja4-vs-ja3/): Chrome/Chromium extension and cipher ordering noise should not create a new identifier every time the same set is shuffled.

`ja4_c` is the extension plus signature-algorithm hash. It is the first 12 hexadecimal characters of SHA-256 over sorted extension hex codes plus signature algorithms in observed order. SNI (`0000`) and ALPN (`0010`) are excluded from the hash input because `ja4_a` already represents them, but they still count in the extension total. That is why two rows can share `t13d1516h2_8daaf6152771` while differing only in the last 12 hex characters: the cipher set and readable shape stayed stable, while extension/signature details changed.

Part A is fast to read, but the full string matters. `t13d1516h2_8daaf6152771_02713d6af862` and `t13d1516h2_8daaf6152771_d8a2da3f94cd` are both Chromium-family shapes in the table, yet they are not the same full fingerprint. A lookup system should key on the full `a_b_c` value when available, then degrade to the prefix only when the log source truncates it.

JA4 also changes when the wire shape changes. Browser version, platform TLS stack, QUIC versus TCP, ALPN list, PSK/resumption behavior, and library updates can all move a client to a new row. JA4DB is the canonical lookup target, but unauthenticated rows were not harvested for this table; the literal hashes above come from public docs, public examples, or live `tls.peet.ws` captures.

## Replaying a fingerprint

`curl_cffi` is the Python binding over the active Lexiforest curl-impersonate fork. Its impersonation targets replay browser-like TLS, JA4, JA3, and HTTP/2 profiles with calls such as `impersonate="chrome131"`, `impersonate="chrome136"`, `impersonate="firefox135"`, or `impersonate="safari260"`. Pin target names for reproducible captures. Generic aliases like `chrome`, `firefox`, and `safari` move as the package adds newer profiles.

Replaying is not the same as becoming a browser. A Chrome-like JA4 paired with Python-ish headers, non-Chrome HTTP/2 SETTINGS, wrong pseudo-header order, or mismatched IP reputation is still inconsistent. The safer workflow is to capture the target profile at `https://tls.peet.ws/api/all`, record `tls.ja4`, compare the HTTP/2 signal, then use that result in tests. The operational library matrix in [the TLS impersonation library comparison](/article/tls-impersonation-library-comparison/)
and the curl_cffi notes in [the curl_cffi TLS fingerprinting notes](/note/tls-fingerprinting-curl-cffi/) cover where TLS replay ends and application-layer parity begins.

For debugging blocks, run the same request body through the normal stack and an impersonated stack from the same egress. If both fail the same way, the gate is probably not the ClientHello. If the impersonated path changes the result, inspect JA4, JA3, HTTP/2 SETTINGS, header order, cookies, and origin behavior before assuming the hash alone was decisive.

## Sources

- [FoxIO JA4 repository](https://github.com/FoxIO-LLC/ja4) — canonical JA4+ spec and tooling entrypoint.
- [FoxIO JA4 technical details](https://github.com/FoxIO-LLC/ja4/blob/main/technical_details/JA4.md) — algorithm source for `ja4_a`, `ja4_b`, and `ja4_c` construction.
- [JA4DB](https://ja4db.foxio.io/) — official lookup target for JA4+ fingerprints, applications, and detection logic.
- [curl_cffi impersonation targets](https://curl-cffi.readthedocs.io/en/stable/impersonate/targets.html) — supported Chrome, Firefox, Safari, and mobile target names.
- [curl_cffi repository](https://github.com/lexiforest/curl_cffi) — the Python binding that replays browser TLS/JA3/JA4/HTTP2 profiles.
- [curl-impersonate (Lexiforest fork)](https://github.com/lexiforest/curl-impersonate) — the actively maintained curl-impersonate fork curl_cffi builds on.
- [tls.peet.ws API](https://tls.peet.ws/api/all) — live TLS/JA3/JA4/HTTP2 reflection endpoint used for capture rows.
- [Cloudflare JA4 Signals](https://blog.cloudflare.com/ja4-signals/) — public JA4 deployment context and Chrome extension-hash example.
- [bunny.net JA4 fingerprinting docs](https://docs.bunny.net/cdn/security/ja4-fingerprinting) — CDN header behavior and published `t13d1516h2_8daaf6152771_02713d6af862` example.
- [Google Threat Intelligence JA4 deep dive](https://security.googlecloudcommunity.com/community-blog-42/ja4-fingerprinting-in-gti-deep-dive-6043) — `behavior_network:` lookup example using the published Chrome-family string.
- [Telegram Desktop issue #30733](https://github.com/telegramdesktop/tdesktop/issues/30733) — public occurrence of `t13d1516h2_8daaf6152771_d8a2da3f94cd` as a Chrome-family mismatch report.