What a TLS fingerprint is and why your agent already has one
Every time your agent opens an https connection, it introduces itself before it sends a single header. That introduction is the TLS handshake, and the way your client words it is distinctive enough that servers can recognise the software behind it. That recognisable pattern is a TLS fingerprint.
I run agents out of Singapore, and this topic usually surfaces when something works from a laptop browser and behaves differently from a script on a server. The cause is often not the IP address or the user agent string. It is the handshake. If you build or operate agents, you already have a TLS fingerprint whether you chose one or not, and it helps to know what it says about you.
What it is
A TLS fingerprint is a compact summary of the choices a client announces at the start of a TLS connection. It is not a secret token or a cookie. Nothing is stored on your machine. It is derived from the first message, the ClientHello, which every client has to send in the clear so the server knows how to talk to it.
The ClientHello lists things like:
- the TLS versions the client supports
- the cipher suites it will accept, in order of preference
- the extensions it wants to use
- the elliptic curves and key share groups it supports
- the application protocols it can speak, such as HTTP/2 or HTTP/1.1
Different software makes different choices. Chrome, Firefox, Safari, Python’s requests library, Go’s net/http, curl and Node.js each ship with their own TLS library and defaults, so each produces a different ClientHello. Fingerprinting just turns that list into a short string or hash that you can compare, count and store.
The two names you will meet are JA3 and JA4. JA3 was published by Salesforce engineers in 2017 and is documented in the salesforce/ja3 repository on GitHub. JA4 came from FoxIO in 2023 and is documented in the FoxIO-LLC/ja4 repository. Both are open specifications, which is partly why they spread so widely.
How it works
The handshake in TLS 1.3 is defined in RFC 8446. The client speaks first. Its ClientHello carries the fields above, and the server answers with its own choices. Because the ClientHello goes out before encryption is established, anything on the path, including the server, a CDN edge or a load balancer, can read it.
JA3 takes five fields from that message: the TLS version, the cipher suites, the extension list, the supported elliptic curves and the elliptic curve point formats. It joins them into one string in the order they appear, then takes an MD5 hash of the string. The result is a 32 character hex digest. Same software, same settings, same hash.
JA3 has a weakness, and the history here is useful. It depends on the order of fields. In early 2023, from Chrome 110 onward, Chrome began randomising the order of TLS extensions in its ClientHello. That meant one browser could produce many different JA3 hashes, which made JA3 less useful for identifying Chrome specifically. Related to this, RFC 8701 defines GREASE, a mechanism where clients insert random reserved values into the lists so that servers do not break when they meet unknown values. Fingerprinting tools have to strip those out.
JA4 was designed with that in mind. It sorts the cipher and extension lists before hashing, so random ordering does not change the result. It also keeps a human readable prefix in front of the hashes. From the FoxIO documentation, that prefix encodes things like whether the connection is TCP or QUIC, the TLS version, whether an SNI value was sent, how many ciphers and extensions were offered, and the first ALPN value (the application protocol negotiation from RFC 7301). You can often read a JA4 string and guess roughly what kind of client sent it before you look anything up.
Note what is not in there. The IP address, the HTTP headers, the cookies and the page content are separate layers. The fingerprint sits underneath all of them.
Why it matters
Here are the four situations where I have seen it come up.
- Defence and abuse detection: CDNs and web application firewalls use fingerprints as one signal among many. Cloudflare documents how it exposes JA3 and JA4 values for its customers in its bots documentation. If a request claims to be Chrome in its user agent but the handshake looks like a Python HTTP library, that mismatch is a data point a site can act on.
- Debugging mystery differences: when the same URL returns a normal page to a browser and an error or a challenge to your script, with the same IP and the same headers, the handshake is a candidate cause. Knowing it exists saves hours of staring at headers.
- Security monitoring on your own side: if you run services, you can log fingerprints of incoming clients to spot unusual software hitting an endpoint, or to notice that one client suddenly changed library after an upgrade.
- Choosing how your agent talks to the web: a full browser driven by an automation framework produces a real browser handshake. A bare HTTP library does not. That is one concrete difference between the two approaches, and it belongs next to the cost and reliability trade-offs I wrote about in attaching to an existing Chrome session vs launching a fresh browser.
A practical consequence for operators: if your agent drives a real browser, your TLS fingerprint is that browser’s fingerprint, and you do not have to think about it much. If your agent makes direct requests from a library, the fingerprint is that library’s, and sites that look at it will see a library. Neither is wrong. They are just different, and you should know which one you are.
I should be clear about scope. This article is about understanding the signal so you can debug and design sensibly, for your own properties and for work that is within each site’s terms. It is not a guide to getting around a site’s protections, and I do not cover that.
Common misconceptions
“A fingerprint identifies me personally.” It does not. A TLS fingerprint describes software, not a person. Millions of people running the same Chrome version on the same operating system produce the same value. It narrows down what kind of client you are, not who you are.
“Changing my user agent changes my fingerprint.” It does not. The user agent is an HTTP header, sent after the TLS connection is already established. The fingerprint was fixed earlier, in the ClientHello. Setting a Chrome user agent on a Python script gives you a script that claims to be Chrome while its handshake says otherwise.
“A new IP address gives me a new fingerprint.” Also no. The IP address is a network layer property and the fingerprint is a property of your TLS library and its configuration. They are independent signals. If you are testing how your own site treats a Singapore mobile connection, a real mobile IP is a reasonable tool for that, and Singapore Mobile Proxy sells real Singapore mobile IPs with sticky sessions (Singapore only). But it changes where you appear to connect from, not what your handshake looks like. For how IP behaviour differs between sticky and rotating setups, see sticky sessions vs rotating IPs for AI agents.
“JA3 is the whole story.” As of October 2026, JA3 is still widely quoted, but because of extension randomisation in modern browsers, it is a weaker identifier than it was around 2018. JA4 and similar methods exist because of that. Vendors also combine the handshake with HTTP/2 settings, header order and behaviour over time. A fingerprint is one input to a decision, not the decision itself.
Where to go from here
If this was new to you, these are the next things I would read or try.
- Read the primary specs briefly: skim the ClientHello section of RFC 8446 to see the fields yourself, then the JA4 README to see how they are summarised.
- Look at your own handshake: capture a connection from your agent with Wireshark and find the ClientHello. Seeing your own cipher list makes the idea concrete. Do this on your own machine and your own traffic.
- Learn the session side: a fingerprint is only one part of how a site recognises a returning client. How long your agent keeps a session alive matters too, which I cover in session lifecycles for long-running agents.
- Get better at tracing: when something behaves differently between environments, you need good visibility into what your agent actually sent. See best tools for tracing and debugging AI agents.
The full list of explainers and how-tos is on the blog index.
Written by Xavier Fok
disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-10-21.