undetected-chromedriver: setup, errors and hCaptcha.
undetected-chromedriver is a Python package that makes a
Selenium-driven Chrome look less automated. How to install it, fix the
version-mismatch and distutils errors, use a proxy, and what
to do when a site shows hCaptcha.
What undetected-chromedriver is
Selenium drives Chrome through ChromeDriver, and a Chrome started that way
reports itself: navigator.webdriver reads true,
and ChromeDriver adds variables of its own to the page that a script can
look for.
undetected-chromedriver downloads a ChromeDriver, patches it, and starts
Chrome so those values read like an ordinary browser’s. Its
README
describes it as an “Optimized Selenium Chromedriver patch which does not
trigger anti-bot services like Distill Network / Imperva / DataDome /
Botprotect.io” that “Automatically downloads the driver binary and patches
it.”
| Package | Maintainer | Latest version | Licence and requirements |
|---|---|---|---|
undetected-chromedriver (PyPI), imported as undetected_chromedriver | UltrafunkAmsterdam, ultrafunkamsterdam on GitHub | 3.5.5, released 17 February 2024 | GPL-3.0. Python only. Needs Selenium 4.9 or newer, requests and websockets, plus a Chrome or Chromium you install yourself |
uc.Chrome is a subclass of Selenium’s Chrome driver, so
find_element, execute_script and the rest of the
Selenium API work unchanged.
Is it still maintained?
Not actively. Version 3.5.5 on
PyPI,
released on 17 February 2024, is still the latest, and it is what
pip install undetected-chromedriver
gives you. The GitHub repository’s last commit, on 5 July 2025, is a
Python 3.13 compatibility fix that has not been released to PyPI. The issue
tracker is turned off; the README sends questions to GitHub Discussions.
The author’s newer project is nodriver, which its PyPI page calls “the official successor of the Undetected-Chromedriver python package”. It drops Selenium and ChromeDriver altogether and has a different, async API, so moving to it means rewriting your script, not changing an import. Other developers have also published forks of undetected-chromedriver; those are not the original author’s. The nodriver guide covers installing nodriver, proxies and its own fork, zendriver.
Install it
pip install undetected-chromedriver
pip install setuptools # Python 3.12 and newer, see below
The package does not bring a browser: it finds the Chrome already installed
on your machine, or the one you point it to with
browser_executable_path. On first
run it downloads a ChromeDriver into a folder of its own
(~/.local/share/undetected_chromedriver
on Linux, ~/Library/Application Support/undetected_chromedriver
on macOS, %APPDATA%\undetected_chromedriver
on Windows).
“No module named ‘distutils’”
On Python 3.12 and newer, a fresh install of 3.5.5 fails on import:
File ".../undetected_chromedriver/patcher.py", line 4, in <module>
from distutils.version import LooseVersion
ModuleNotFoundError: No module named 'distutils'
Python 3.12 removed distutils, and the released version still
imports it. Installing setuptools into the same environment puts
it back; we reproduced the error on Python 3.12 and 3.13 and the import
worked after pip install setuptools.
The GitHub version no longer needs it, but it is not on PyPI.
Quick start with Selenium
import undetected_chromedriver as uc
driver = uc.Chrome(headless=True) # uc.Chrome() opens a visible window
driver.get("https://your-site.example/")
print(driver.execute_script("return navigator.webdriver")) # False
print(driver.title)
driver.quit()
We ran this on 2 November 2026 with undetected-chromedriver 3.5.5,
Selenium 4.52.0 and Chrome 156, headless. Plain Selenium’s
webdriver.Chrome() read
navigator.webdriver as
true; uc.Chrome() read it as false.
Options go through uc.ChromeOptions(),
which takes the same arguments as Selenium’s. Two settings of its own
are worth knowing: a profile folder that survives the run, and the Chrome
version to fetch a driver for:
import undetected_chromedriver as uc
driver = uc.Chrome(
user_data_dir="/path/to/profile", # created if missing, kept after quit
version_main=156, # fetch the driver for Chrome 156
) Headless mode
The README calls headless “unsupported officially”, and the
headless argument’s own docstring warns that it “lowers
undetectability and not fully supported”. In our run,
uc.Chrome(headless=True) fixed
navigator.webdriver but left HeadlessChrome in the
user agent. Passing a normal user agent yourself removed it:
import undetected_chromedriver as uc
ua = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 "
"(KHTML, like Gecko) Chrome/156.0.0.0 Safari/537.36"
)
options = uc.ChromeOptions()
options.add_argument(f"--user-agent={ua}")
driver = uc.Chrome(options=options, headless=True) Fix the Chrome version mismatch
The project’s Discussions hold 15 threads with this message:
selenium.common.exceptions.SessionNotCreatedException: Message: session not created:
cannot connect to chrome at 127.0.0.1:62047
from session not created: This version of ChromeDriver only supports Chrome version 157
Current browser version is 156.0.8078.79 Unless you tell it otherwise, 3.5.5 downloads the ChromeDriver for the newest stable Chrome, not the Chrome you have installed. Whenever your browser is behind the newest stable release, because an update has not been applied yet or you keep an older Chrome, the session fails. We reproduced the message above by asking for a driver one version ahead of the installed Chrome 156. Two fixes:
- Update Chrome and restart it, so it matches the newest driver.
- Pin the driver to your browser with
version_main:
import undetected_chromedriver as uc
# Your Chrome's major version: chrome://version, or
# google-chrome --version / "Google Chrome.app/Contents/MacOS/Google Chrome" --version
driver = uc.Chrome(version_main=156) version_main is the major version only. In our test,
version_main=156 fetched ChromeDriver
156.0.8078.80 for Chrome 156.0.8078.79 and started cleanly. To keep the
package itself from moving under you, pin it in
requirements.txt or at install time:
pip install "undetected-chromedriver==3.5.5" setuptools Use a proxy
The README is blunt about IP addresses: “THIS PACKAGE DOES NOT, and i repeat DOES NOT hide your IP address, so when running from a datacenter (even smaller ones), chances are large you will not pass!” A proxy is set with Chrome’s own flag:
import undetected_chromedriver as uc
options = uc.ChromeOptions()
options.add_argument("--proxy-server=http://203.0.113.10:8080") # or socks5://host:port
driver = uc.Chrome(options=options)
driver.get("https://your-site.example/")
We checked this against a local test proxy: every page request went through
it. The flag takes a scheme, host and port. Putting
user:pass@ in it does not work: in
our test Chrome never reached the proxy and showed “This site
can’t be reached”. For a proxy that needs a login, allow your
IP address in the provider’s dashboard, or run a local proxy without a
login that forwards to it.
Is there an undetected-chromedriver for C# or Java?
No, not from its author. undetected-chromedriver is Python only, and so is nodriver. Other people have ported the idea:
- C#: the NuGet package
Selenium.UndetectedChromeDriverby fysh711426 calls itself a “C# implementation of undetected_chromedriver”. It was the most downloaded result in a NuGet search when we looked. - Java: we found only small community ports on GitHub, the largest with 22 stars when we looked.
Ports are separate code with their own maintainers, so read what they patch before you rely on them. For hCaptcha in .NET, the C# guide works with plain Selenium WebDriver.
What it does when a site shows hCaptcha
The package’s PyPI summary says it is “Not triggered by CloudFlare/Imperva/hCaptcha and such”, followed by “NOTE: results may vary due to many factors. No guarantees are given, except for ongoing efforts in understanding detection algorithms.” Fewer automation tells are one of several things that decide whether hCaptcha shows a challenge at all; why am I getting hCaptcha covers the others, starting with the reputation of your IP address.
Once a challenge does appear, undetected-chromedriver has nothing more to
offer. The site accepts the form only with a valid token in the
h-captcha-response field, a
P1_ string its server checks with hCaptcha before it lets the
request through. Invisible hCaptcha needs that token too, even when no
puzzle is shown. A patched driver does not produce one. When we opened
hCaptcha’s own demo page with uc.Chrome(), the sitekey
and the empty h-captcha-response
field were there to read; filling that field is the part the package
leaves to you.
Where NoneCap fits
NoneCap is an hCaptcha solving API. Keep undetected-chromedriver for the
rest of your Selenium run, and when a page has hCaptcha, send its
sitekey and url to NoneCap, get a
P1_ token back, put it in the form and submit. With the
Python SDK
(pip install nonecap),
nc.solve() waits for the result, so there is no polling loop:
import os
import undetected_chromedriver as uc
from nonecap import NoneCap
from selenium.webdriver.common.by import By
nc = NoneCap(api_key=os.environ["NONECAP_KEY"])
driver = uc.Chrome()
driver.get("https://your-site.example/signup")
sitekey = driver.find_element(By.CSS_SELECTOR, "[data-sitekey]").get_attribute("data-sitekey")
solve = nc.solve(
type="hcaptcha",
sitekey=sitekey,
url=driver.current_url,
user_agent=driver.execute_script("return navigator.userAgent"),
) Then put the token in the form, call the widget’s callback if the page set one, and submit:
driver.execute_script("""
const token = arguments[0];
for (const name of ["h-captcha-response", "g-recaptcha-response"]) {
const el = document.querySelector(`textarea[name="${name}"]`);
if (el) el.value = token;
}
const cb = document.querySelector("[data-callback]")?.getAttribute("data-callback");
if (cb && typeof window[cb] === "function") window[cb](token);
""", solve.token)
driver.find_element(By.CSS_SELECTOR, "button[type=submit]").click()
driver.quit()
Sending the browser’s own user_agent is optional; some
sites need it for the token to be accepted. If your Chrome goes through a
proxy, you can pass the same one as proxy
(http://user:pass@host:port) so the
token and the form come from the same IP address. Failed, cancelled and
expired solves are never charged.
The Selenium guide covers sitekeys that
are not in the DOM, invisible and enterprise (rqdata) sitekeys,
and the error types the SDK raises. Every field and status is in the
API reference. NoneCap solves
hCaptcha only; it does not help with reCAPTCHA, Turnstile
or other CAPTCHAs.
Last updated November 2026.
Frequently asked
What is undetected-chromedriver?
navigator.webdriver, read like an ordinary browser’s. You use it in place of webdriver.Chrome(); everything else is normal Selenium. It is written by UltrafunkAmsterdam and licensed GPL-3.0.How do I install undetected-chromedriver?
pip install undetected-chromedriver, plus pip install setuptools on Python 3.12 or newer, because version 3.5.5 still imports distutils, which Python 3.12 removed. Chrome itself is not included: install Chrome or Chromium first.Is undetected-chromedriver still maintained?
How do I fix “This version of ChromeDriver only supports Chrome version”?
uc.Chrome(version_main=156).How do I use a proxy with undetected-chromedriver?
options.add_argument("--proxy-server=http://host:port"). The flag has no place for a username and password; in our test, a proxy URL with user:pass@ in it never reached the proxy at all. For a proxy that needs a login, allow your IP address in the provider’s dashboard, or run a local proxy without a login that forwards to it.Is there an undetected-chromedriver for C# or Java?
Selenium.UndetectedChromeDriver by fysh711426 describes itself as a “C# implementation of undetected_chromedriver”. For Java we found only small community ports on GitHub. Check who maintains a port before you depend on it.Does undetected-chromedriver bypass hCaptcha?
h-captcha-response, and a patched driver does not produce one. NoneCap returns that token for hCaptcha.