tlsmask makes HTTP requests from Python that look like they come from Chrome, Firefox or Safari. Not just the headers: the TLS handshake and the HTTP/2 settings match the browser too. It runs bogdanfinn's tls-client, a Go library, and ships its latest official build inside the package.
It is also a drop-in replacement for the tls-client package, which hasn't had a release since February 2024.
import tlsmask
with tlsmask.Session("chrome_150") as session:
r = session.get("https://tls.peet.ws/api/all")
print(r.status_code, r.protocol) # 200 HTTP/2.0
print(r.json()["tls"]["ja4"]) # Chrome's fingerprint, not Python's
Why
Every HTTPS connection starts with a TLS "ClientHello": a list of the ciphers, extensions and curves the client supports, in a particular order. Each browser sends its own recognisable version (often summarised as a JA3 or JA4 fingerprint), and so does Python. A server can tell them apart before it reads a single header, which is why requests with a Chrome User-Agent still doesn't look like Chrome.
bogdanfinn's tls-client reproduces real browsers' handshakes and HTTP/2 behaviour. The tls-client package made it usable from Python, but it stopped at version 1.0.1. It still bundles tls-client v1.7.2, so its newest browser is Chrome 120, and a number of its bugs were never fixed.
tlsmask carries on from there: the current library, the same API, those bugs fixed, and a weekly check for new tls-client releases.
tlsmask compared with tls-client
tls-client (the old wrapper) |
tlsmask | |
|---|---|---|
| bogdanfinn/tls-client inside | v1.7.2 | v1.16.0 |
| Browser & app profiles | 51 | 83 |
| Newest Chrome | 120 | 152 |
| Newest Firefox | 120 | 148 |
| Newest iOS Safari | 16.0 | 26.0 |
| Brave | none | 146 |
| Last updated | Feb 2024 | Sep 2026 |
| Native library checked against a pinned SHA-256 | no | yes, at build and at load |
| Keeps itself current | no | yes, a weekly bot |
Installation
pip install tlsmask
Prebuilt wheels are available for:
- Windows, 64-bit and 32-bit
- macOS 13 or newer, Apple Silicon and Intel
- Linux x86-64, ARM64 and ARMv7 (glibc 2.17 or newer)
Each wheel already contains the tls-client library for its platform, so nothing is downloaded when your code runs. tlsmask needs Python 3.9 or newer and has no other dependencies.
Usage
import tlsmask
session = tlsmask.Session("chrome_150")
session.headers.update(
{
"User-Agent": (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/150.0.0.0 Safari/537.36"
),
"Accept-Language": "en-US,en;q=0.9",
}
)
r = session.get("https://example.com", params={"q": "tls"})
print(r.status_code, r.headers["content-type"])
r = session.post("https://httpbin.org/post", json={"hello": "world"})
print(r.json())
session.close()
With asyncio:
import asyncio
import tlsmask
async def main():
async with tlsmask.AsyncSession("firefox_148") as session:
pages = await asyncio.gather(
*(session.get(f"https://example.com/?page={i}") for i in range(10))
)
print([page.status_code for page in pages])
asyncio.run(main())
The guide covers the rest: header order, cookies, proxies, redirects, timeouts, errors, threads, custom TLS fingerprints and how to check your install.
Switching from tls-client
- import tls_client
+ import tlsmask as tls_client
For most code that's the only change. The constructor arguments, the request methods, response.json() and the session.cookies helpers work the same way. A few things behave differently on purpose, mostly because the old behaviour was a bug:
tls-client |
tlsmask | |
|---|---|---|
Unknown client_identifier (including typos like chrome112) |
silently sends a different browser | raises ValueError and suggests the closest names |
| A panic inside the Go library | kills the whole Python process | raises TLSClientException (catch_panics is on by default) |
proxies={"https": ...} |
ignored, so the request goes out without the proxy | used |
post(json=...) |
adds Content-Type to every later request |
adds it to that request only |
| Binary responses (images, PDFs) | corrupted | returned byte for byte |
| Header order | tls-client's own order | the order you wrote them in (or header_order=) |
cert_compression_algo |
silently ignored by current tls-client | translated to the new setting |
params on a URL that already has ? |
url?a=1?b=2 |
url?a=1&b=2 |
| Deleting a cookie | still sent, because a second copy lives inside Go | gone from the next request |
Different timeout_seconds or insecure_skip_verify per request |
ignored after the first request | honoured every time |
| Default profile | chrome_120 |
tls-client's current default (tlsmask.DEFAULT_PROFILE) |
get(url, cookies={...}) |
also stored those cookies in session.cookies |
sends them with that request only, and only to that site, even across redirects |
A proxy written as host:port |
failed | treated as http://host:port, like requests |
Authorization on a redirect |
kept whenever the host name matches | kept only on the same origin, or on an upgrade from http to https |
client_identifier=None |
built a client from whatever was set, failing later if something was missing | checks up front that ja3_string and the settings its extensions need are there, and raises ValueError naming what's missing; ALPN defaults to what browsers send |
additional_decode= |
decoded one extra encoding | not needed any more (tls-client decodes everything); it warns and is ignored |
The old misspelled TLSClientExeption still works. If your code imports tls_client.settings.ClientIdentifiers, use tlsmask.ClientIdentifiers instead. response.headers is case-insensitive now, so use dict(response.headers) where you need a plain dict (for json.dumps, say).
Choosing a profile
For most uses, pick a current browser, such as the default (tlsmask.DEFAULT_PROFILE), firefox_148 or safari_ios_26_0. Send a User-Agent that matches the profile, because a Firefox User-Agent over a Chrome handshake stands out on its own. The _PSK variants resume TLS sessions the way a returning visitor's browser does.
tlsmask.PROFILES holds every name, and editors autocomplete them through the tlsmask.ClientIdentifier type.
All 83 profiles in tls-client v1.16.0
| Family | Profiles |
|---|---|
| Chrome | chrome_103 chrome_104 chrome_105 chrome_106 chrome_107 chrome_108 chrome_109 chrome_110 chrome_111 chrome_112 chrome_116_PSK chrome_116_PSK_PQ chrome_117 chrome_120 chrome_124 chrome_130_PSK chrome_131 chrome_131_PSK chrome_133 chrome_133_PSK chrome_144 chrome_144_PSK chrome_146 chrome_146_PSK chrome_150 chrome_150_PSK chrome_152 chrome_152_PSK |
| Firefox | firefox_102 firefox_104 firefox_105 firefox_106 firefox_108 firefox_110 firefox_117 firefox_120 firefox_123 firefox_132 firefox_133 firefox_135 firefox_146_PSK firefox_147 firefox_147_PSK firefox_148 |
| Safari (iOS & iPadOS) | safari_ios_15_5 safari_ios_15_6 safari_ios_16_0 safari_ios_17_0 safari_ios_18_0 safari_ios_18_5 safari_ios_26_0 safari_ipad_15_6 |
| Safari (macOS) | safari_15_6_1 safari_16_0 |
| Opera | opera_89 opera_90 opera_91 |
| Brave | brave_146 brave_146_PSK |
| Mobile apps & others | cloudscraper confirmed_android confirmed_ios mesh_android mesh_android_1 mesh_android_2 mesh_ios mesh_ios_1 mesh_ios_2 mms_ios mms_ios_1 mms_ios_2 mms_ios_3 nike_android_mobile nike_ios_mobile okhttp4_android_7 okhttp4_android_8 okhttp4_android_9 okhttp4_android_10 okhttp4_android_11 okhttp4_android_12 okhttp4_android_13 zalando_android_mobile zalando_ios_mobile |
Where the library comes from
The native part of tlsmask is bogdanfinn's own release file, byte for byte. It isn't rebuilt or modified.
- When a wheel is built, the file is downloaded from tls-client's GitHub releases. The build refuses it unless its SHA-256 matches both the hash pinned in
tlsmask/_pins.pyand the digest GitHub publishes for the release. - The first time your program makes a request, tlsmask hashes the file again and won't load it if it doesn't match.
- Releases are published to PyPI from GitHub Actions with trusted publishing and signed attestations, and every GitHub release includes a
SHA256SUMSfile.
To check your own installation:
$ python -m tlsmask
tlsmask 1.16.0
tls-client 1.16.0 (github.com/bogdanfinn/tls-client)
platform win32/AMD64 (64-bit Python) -> windows-amd64
library OK: ...\tlsmask\bin\tls-client-windows-64-1.16.0.dll (sha256 53dca636..., as pinned)
Staying up to date
Every Monday a scheduled job looks for a new tls-client release. When there is one, it downloads and verifies every file, reads the new list of profiles, compares the library's API with the previous version, and runs the full test suite on Windows, macOS and Linux, on both Intel and ARM machines.
If nothing important changed and the release is at least a week old, a new tlsmask version is published automatically. Anything unusual, like a removed profile or a changed API, waits for a person to look at it first.
FAQ
Is this an official bogdanfinn project? No. tlsmask is an independent project and isn't affiliated with or endorsed by Bogdan Finn. It bundles his library unmodified. Please report tlsmask problems here rather than to him.
Does it work on Alpine Linux?
Not at the moment. tls-client's Alpine (musl) build can't be loaded into Python, so there is no Alpine wheel. In Docker, use a glibc-based image such as python:3.12-slim instead.
My platform has no wheel.
Get a build of tls-client for it (from its releases, or build it yourself) and point tlsmask at the file with the TLSMASK_LIBRARY_PATH environment variable. On such a platform pip install tlsmask installs from the source distribution, which has no library of its own.
Does it work with multiprocessing?
Yes. The library is only loaded when the first request is made, so child processes are fine as long as the parent hadn't made a request before it forked. If in doubt, use the spawn start method (the default on Windows and macOS).
Why aren't redirects followed by default?
tls-client didn't follow them by default, so existing code keeps working. Pass allow_redirects=True.
What is it for? Making requests that look like a real browser: testing your own site's bot protection, research, or talking to APIs that turn away non-browser clients. Please respect the terms of the sites you use it with.
Contributing
Bug reports and pull requests are welcome. To set up a development copy:
git clone https://github.com/Ubaidullah71/tlsmask && cd tlsmask
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -e ".[dev]"
python scripts/fetch_binaries.py # downloads and verifies this machine's library into tlsmask/bin/
pytest # unit tests, plus tests against the real library and a local server
pytest -m live # fingerprint checks against a public echo service
tlsmask/_pins.py, tlsmask/profiles.py, tlsmask/_version.py and the tables in this README are generated by scripts/render.py, so please don't edit them by hand.
Credits
- bogdanfinn/tls-client does the real work. This product includes software developed by Bogdan Finn, distributed under the BSD 4-Clause license (see LICENSE-tls-client).
- FlorianREGAZ/Python-Tls-Client designed the Python API that tlsmask keeps compatible.
License
tlsmask's own code is under the MIT license. The bundled tls-client library is under the BSD 4-Clause license (LICENSE-tls-client).
Metadata
Release files for tlsmask 1.16.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| tlsmask-1.16.0.tar.gz | 69.5 kB | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| tlsmask-1.16.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| tlsmask-1.16.0-py3-none-win32.whl | Python 3 | none | Windows x86-32 | Details |
| tlsmask-1.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| tlsmask-1.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl | Python 3 | none | Linux glibc 2.17+ ARMv7l | Details |
| tlsmask-1.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| tlsmask-1.16.0-py3-none-macosx_13_0_x86_64.whl | Python 3 | none | macOS 13.0+ x86-64 | Details |
| tlsmask-1.16.0-py3-none-macosx_13_0_arm64.whl | Python 3 | none | macOS 13.0+ ARM64 | Details |
Total release size: 50.7 MB
Release files / tlsmask-1.16.0.tar.gz
| Download URL | tlsmask-1.16.0.tar.gz |
|---|---|
| Size | 69.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ba38f3a154ad2a7e925c0d43cf760134f39bf727b86479dc96849374a31256c9
|
|
BLAKE2b-256 checksum How to use checksums |
340841e9153505346904d01d7bf7628866eb0135ffb372b794ec9ee0e70292e4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / tlsmask-1.16.0-py3-none-win_amd64.whl
| Download URL | tlsmask-1.16.0-py3-none-win_amd64.whl |
|---|---|
| Size | 8.4 MB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
b532df1e48b125345f97207040595f543db7e54df11848b7bfbbb8aa6d3f2765
|
|
BLAKE2b-256 checksum How to use checksums |
f925d37f001a41fbaf83352c57b6639c33ec269e996e1341b26d4ce5736268f0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / tlsmask-1.16.0-py3-none-win32.whl
| Download URL | tlsmask-1.16.0-py3-none-win32.whl |
|---|---|
| Size | 8.3 MB |
| Tags | Python 3 Windows x86-32 |
|
SHA-256 checksum How to use checksums |
75b86e4476e3066fbecb99fc6ae503fdbb4802980955812efc75da5dd9823968
|
|
BLAKE2b-256 checksum How to use checksums |
e8dce512fa1f6c9f538fdbf97cc4cf72f185efd41b33656439a79f62df180017
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / tlsmask-1.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | tlsmask-1.16.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 8.5 MB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
07b9826ec5c3493a06ed16de19ae0b9b6115f4b3069dfeb671390d73bbf93818
|
|
BLAKE2b-256 checksum How to use checksums |
2ede53249d9bf1e4bab6a5763e13582609354fc5474f72fcf4928678fc06920e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / tlsmask-1.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
| Download URL | tlsmask-1.16.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl |
|---|---|
| Size | 8.1 MB |
| Tags | Linux glibc 2.17+ ARMv7l Python 3 |
|
SHA-256 checksum How to use checksums |
35917ae97e275f5f23de170d112431f7618490db4c932fd4b370fb799ae0a633
|
|
BLAKE2b-256 checksum How to use checksums |
4f6c3d6e4b972de244460d02187d2caaf0e0606c3a58697e6069f9d6cb1bc3a4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / tlsmask-1.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | tlsmask-1.16.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 7.9 MB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
786be842c20586f427e86388c84b28318034d9f3f668d919bba1439cfc2f1763
|
|
BLAKE2b-256 checksum How to use checksums |
03890f9f15728a49ba947a5c356285e1f4d6241e39a8d363107af72f71dc7450
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / tlsmask-1.16.0-py3-none-macosx_13_0_x86_64.whl
| Download URL | tlsmask-1.16.0-py3-none-macosx_13_0_x86_64.whl |
|---|---|
| Size | 4.8 MB |
| Tags | Python 3 macOS 13.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
b0b86957d5dfb94eda05875d80ddda657144dce7f83af065e214b9a30777e92e
|
|
BLAKE2b-256 checksum How to use checksums |
b3fc25092c28e8f4bbab9b3a6aa9de9792c8f74e17fedf452b52cdf2d547663e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency logRelease files / tlsmask-1.16.0-py3-none-macosx_13_0_arm64.whl
| Download URL | tlsmask-1.16.0-py3-none-macosx_13_0_arm64.whl |
|---|---|
| Size | 4.5 MB |
| Tags | Python 3 macOS 13.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
7ff314e9161d7d3d75c67897b612021c51650e34288860387af913e43809e7a9
|
|
BLAKE2b-256 checksum How to use checksums |
e5cf08363dedd421e9f2ff28020f38737371726ad972932b7806aa43344873d1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.
Transparency log