Pure Python TLS fingerprint spoofing client with browser challenge fallback.
Project description
ViperTLS
Pure Python HTTP client that makes your requests look like a real browser at the TLS level. No curl_cffi. No Go binary. No compiled extensions.
Spoofs JA3/JA4 fingerprints, HTTP/2 SETTINGS frames, HTTP/2 pseudo-header order, and HTTP header ordering. When a site still throws a JS challenge, ViperTLS falls back to a real Chromium browser, captures the clearance cookies, and reuses them on later requests — automatically.
Install
pip install vipertls
vipertls install-browsers
vipertls install-browsers downloads the Playwright Chromium binary used for browser challenge fallback. On Windows, nothing else is needed. On Linux, see System Requirements below.
Python 3.10 through 3.14 supported.
Quick Start
import asyncio
import vipertls
async def main():
async with vipertls.AsyncClient(impersonate="chrome_145") as client:
r = await client.get("https://www.crunchyroll.com/")
print(r.status_code)
print(r.solved_by)
asyncio.run(main())
Three Ways to Use It
1. Python module
import asyncio
import vipertls
async def main():
async with vipertls.AsyncClient(impersonate="edge_133") as client:
r = await client.get("https://example.com")
print(r.status_code, r.solved_by)
asyncio.run(main())
2. Local proxy server
Run a local HTTP server and hit any target through it using X-Viper-* headers.
vipertls serve --host 127.0.0.1 --port 8080
curl http://127.0.0.1:8080 \
-H "X-Viper-URL: https://example.com" \
-H "X-Viper-Impersonate: chrome_145"
The CLI launches a live TUI dashboard showing every request in real time — status, solve mode, timing, and preset.
3. Standalone browser solver API
Only need the challenge-solving side as a service:
python -m vipertls.solver --host 127.0.0.1 --port 8081
curl -X POST http://127.0.0.1:8081/solve \
-H "content-type: application/json" \
-d '{"url":"https://example.com","preset":"edge_133","timeout":30}'
Endpoints: POST /solve, DELETE /cookies/{domain}, DELETE /cookies, GET /health
Async Client
import asyncio
import vipertls
async def main():
async with vipertls.AsyncClient(
impersonate="chrome_145",
proxy="socks5://user:pass@host:1080",
timeout=30,
verify=True,
follow_redirects=True,
debug_messages=False,
) as client:
r = await client.get("https://example.com/")
print(r.status_code, r.http_version, len(r.content))
print(r.solved_by, r.from_cache)
asyncio.run(main())
HTTP/3
async with vipertls.AsyncClient(impersonate="chrome_145", http3=True) as client:
r = await client.get("https://example.com/")
print(r.http_version)
Pass http3=True to force QUIC. Without it, ViperTLS still upgrades automatically when it sees an alt-svc: h3= header from the server.
Sync Client
import vipertls
client = vipertls.Client(impersonate="firefox_136", timeout=30)
r = client.get("https://example.com/")
print(r.status_code)
print(r.text[:500])
Response Object
r.status_code
r.ok
r.headers
r.content
r.text
r.json()
r.url
r.http_version
r.solved_by
r.from_cache
r.cookies_received
r.cookies_used
r.solve_info
r.raise_for_status()
Solver States
r.solved_by tells you how ViperTLS got through:
| Value | Meaning |
|---|---|
tls |
TLS fingerprint was enough — fast, no browser |
browser |
Site required a JS challenge — Playwright solved it |
cache |
Earlier browser solve still valid — cookies reused |
JA4 Fingerprint Family
Every response exposes the full JA4 suite so you can verify what your request looked like from the server side:
r.ja4 # TLS ClientHello fingerprint
r.ja4_r # JA4 raw (unsorted, full values)
r.ja4h # HTTP header fingerprint
r.ja4s # Server hello fingerprint
r.ja4l # Latency fingerprint (connect_ms_handshake_ms)
r.solve_info # all of the above as a dict
Proxy Support
AsyncClient(proxy="socks5://user:pass@host:1080")
AsyncClient(proxy="socks5h://user:pass@host:1080")
AsyncClient(proxy="socks4://host:1080")
AsyncClient(proxy="http://user:pass@host:8080")
AsyncClient(proxy="host:port")
AsyncClient(proxy="host:port:user:pass")
Bare host:port and host:port:user:pass are treated as HTTP CONNECT proxies automatically.
Error Handling
from vipertls import AsyncClient, ViperHTTPError, ViperConnectionError, ViperTimeoutError
try:
async with AsyncClient(impersonate="chrome_145") as client:
r = await client.get("https://example.com/")
r.raise_for_status()
except ViperTimeoutError:
pass
except ViperConnectionError:
pass
except ViperHTTPError as e:
print(e.response.status_code)
Runtime Helpers
import vipertls
print(vipertls.get_runtime_paths())
vipertls.clear_solver_cache()
vipertls.clear_solver_cache(domain="1337x.to")
vipertls.clear_solver_cache(domain="1337x.to", preset="edge_133")
Browser Presets
| Preset | JA4 |
|---|---|
chrome_145 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
chrome_140 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
chrome_136 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
chrome_133 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
chrome_131 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
chrome_124 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
chrome_120 |
t13d1515h2_dea800f94266_daa8e4778a3e |
firefox_136 |
t13d1814h2_f3ddd0d8df11_3bed559cf7b0 |
firefox_133 |
t13d1814h2_f3ddd0d8df11_3bed559cf7b0 |
firefox_127 |
t13d1814h2_f3ddd0d8df11_3bed559cf7b0 |
firefox_120 |
t13d1814h2_f3ddd0d8df11_3bed559cf7b0 |
safari_18 |
t13d2214h2_bb4723730337_030652283baa |
safari_17 |
t13d2214h2_bb4723730337_030652283baa |
edge_136 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
edge_133 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
brave_136 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
opera_117 |
t13d1516h2_dea800f94266_0cba2f92bfc0 |
Short aliases: chrome → chrome_145, firefox → firefox_136, safari → safari_18, edge → edge_136, brave → brave_136, opera → opera_117
Which preset to use:
chrome_145— best default for TLS-only targets and invisible Cloudflareedge_133— best default when you expect a JS challenge fallbackfirefox_136— when you specifically need Firefox TLS/HTTP2 behaviorsafari_18— when the target checks for Safari-specific fingerprints
Proxy Server Control Headers
| Header | Description | Example |
|---|---|---|
X-Viper-URL |
Target URL | https://example.com/api |
X-Viper-Method |
HTTP method | POST |
X-Viper-Impersonate |
Browser preset | chrome_145 |
X-Viper-Proxy |
Proxy URL | socks5://user:pass@host:1080 |
X-Viper-Timeout |
Timeout in seconds | 30 |
X-Viper-JA3 |
Override JA3 string | 771,4865-4866-4867,... |
X-Viper-No-Redirect |
Disable redirect following | true |
X-Viper-Skip-Verify |
Skip TLS cert verification | true |
X-Viper-Force-HTTP1 |
Force HTTP/1.1 | true |
X-Viper-Body |
Request body as string | {"key":"value"} |
X-Viper-Headers |
Extra headers as JSON | {"authorization":"Bearer ..."} |
Response includes: X-ViperTLS-Solved-By, X-Viper-HTTP-Version, X-ViperTLS-JA4, X-ViperTLS-JA4H, X-ViperTLS-JA4S, X-ViperTLS-JA4L, X-Viper-Received-Cookies, X-ViperTLS-Used-Cookies
Hosting
Railway / Render
web: python -m vipertls serve --host 0.0.0.0 --port $PORT
Docker
docker build -t vipertls .
docker run -p 8080:8080 vipertls
VPS
pip install vipertls
vipertls install-browsers
python -m vipertls serve --host 0.0.0.0 --port 8080 --workers 4
Hosted deployment notes:
- Python 3.10+ required; 3.12 or 3.13 most battle-tested on cloud runtimes
- Linux needs Playwright system libraries for browser solving (see below)
- If the platform blocks system package installs, TLS mode still works — only browser challenge fallback is affected
System Requirements (Linux / macOS)
The browser fallback runs real Chromium. It needs OS-level shared libraries.
macOS
No extra steps after vipertls install-browsers. If Chromium crashes:
brew install --cask xquartz
Ubuntu / Debian / Kali
Easiest — let Playwright install everything:
playwright install --with-deps chromium
Manual install if preferred:
sudo apt-get install -y \
libglib2.0-0 libnss3 libnspr4 libatk1.0-0 \
libatk-bridge2.0-0 libcups2 libdrm2 libdbus-1-3 \
libxcb1 libxkbcommon0 libx11-6 libxcomposite1 \
libxdamage1 libxext6 libxfixes3 libxrandr2 \
libgbm1 libasound2 libpango-1.0-0 libcairo2 \
libexpat1 libudev1
Arch / Manjaro
sudo pacman -S --needed \
glib2 nss nspr atk at-spi2-atk at-spi2-core \
cups libdrm dbus libxcb libxkbcommon \
libx11 libxcomposite libxdamage libxext libxfixes libxrandr \
mesa libgbm alsa-lib pango cairo expat systemd-libs
Fedora / RHEL / CentOS
sudo dnf install -y \
glib2 nss nspr atk at-spi2-atk at-spi2-core \
cups-libs libdrm dbus-libs libxcb libxkbcommon \
libX11 libXcomposite libXdamage libXext libXfixes libXrandr \
mesa-libgbm alsa-lib pango cairo expat systemd-libs
NixOS / Replit
Add to replit.nix (or shell.nix / flake.nix):
{pkgs}: {
deps = [
pkgs.glib pkgs.nss pkgs.nspr pkgs.atk
pkgs.at-spi2-atk pkgs.at-spi2-core pkgs.dbus
pkgs.cups pkgs.libdrm pkgs.mesa pkgs.libgbm
pkgs.libxkbcommon pkgs.alsa-lib pkgs.expat
pkgs.udev pkgs.pango pkgs.cairo pkgs.gtk3
pkgs.xorg.libX11 pkgs.xorg.libXcomposite
pkgs.xorg.libXdamage pkgs.xorg.libXext
pkgs.xorg.libXfixes pkgs.xorg.libXrandr
pkgs.xorg.libxcb pkgs.xorg.libXcursor
pkgs.xorg.libXi
];
}
Then run vipertls install-browsers — NixOS is auto-detected and --with-deps is skipped automatically.
Verifying your setup
python3 -c "
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
b = p.chromium.launch(headless=True)
page = b.new_page()
page.goto('https://example.com')
print('OK:', page.title())
b.close()
"
If that prints OK: Example Domain you're good. If it throws a library error on Linux:
python3 -c "
from playwright._impl._driver import compute_driver_executable
import pathlib
print(pathlib.Path(compute_driver_executable()).parent.parent)
"
ldd ~/.cache/ms-playwright/chromium-*/chrome-linux64/chrome | grep "not found"
Any not found entries are the missing libs — install the matching package for your distro.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vipertls-0.1.8.tar.gz.
File metadata
- Download URL: vipertls-0.1.8.tar.gz
- Upload date:
- Size: 57.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7e0be40ba1e4bafcfe61bac3864dc0f9b36dad1a9e069dd5d14680af8993f54e
|
|
| MD5 |
76d9e61108a1b7401ca2866eb6800e86
|
|
| BLAKE2b-256 |
53d92e9c7998ea4281a2a00e78a8c04f00d4362592dc06519a0f87817a696f3b
|
File details
Details for the file vipertls-0.1.8-py3-none-any.whl.
File metadata
- Download URL: vipertls-0.1.8-py3-none-any.whl
- Upload date:
- Size: 54.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cda92301f3bdc9d6ace601adf330fc29e7f9f8c18beaba6882ebf9ea1ea634ca
|
|
| MD5 |
aade29a689a1e2d82835db1ebb19ca43
|
|
| BLAKE2b-256 |
7a181bdc5aaafccd83d2ba408248402c74669c2a049887efe50bc49c3aef4f96
|