Integrate a CAPTCHA-Solving API with Selenium (Python and Node)

· All articles

Selenium is still the most common way teams automate a website, and the most common way those teams get stuck: the form fills fine until the CAPTCHA page appears, and no amount of click() gets past it. This guide builds the integration that does work — request a token from the EndCaptcha API, inject it into the page the driver is on, submit while it is fresh — in Python and in Node, with code that runs against the clients we ship.

Why Selenium runs into the challenge

A site's rules do not care that your script is honest; they care that the visit looks automated. Selenium gives them the tells: the navigator.webdriver flag on window, a browser user agent with no plugins, a page that loads and submits faster than any human manages. When the rules fire, the challenge appears — usually as an interstitial before the form you wanted, or as a reCAPTCHA widget rendered inside iframe[title*="reCAPTCHA"].

Chasing the widget from the driver is a dead end. Clicking the checkbox means switching into the iframe, clicking an element that may not be there yet, and repeating until the site decides you are a robot — which it already suspected. The reliable unit of work is a token the page accepts, produced outside the browser, not a battle with the widget.

The integration pattern

Every CAPTCHA-solving integration with Selenium is the same three steps:

  1. Request the token with the details of the page the driver will be on: the site key and the full URL of that page.
  2. Inject the token into the response field the page reads.
  3. Submit while the token is still valid — tokens are single-use and expire in about two minutes.

The solve happens outside the browser, which is what makes this shape portable: it works headless, on a Selenium Grid node, behind a proxy, and with the same code whether the page shows reCAPTCHA v2, v3 or a Cloudflare Turnstile. There is no browser farm to run and no widget to train.

Step one: get the token (Python)

The Python client ships in the downloads of the API documentation page. For a reCAPTCHA v2 widget you send the site key and the full URL of the page that hosts it:

import json
from endcaptcha import HttpClient

client = HttpClient("YOUR_USERNAME", "YOUR_PASSWORD")

captcha = client.decode(
    type=4,
    token_params=json.dumps({
        "googlekey": "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI",
        "pageurl": "https://www.example.com/signup",
    }),
)

token = captcha["text"]

decode() does the upload and the polling for you. If you poll by hand, know the protocol: right after upload the API answers UNSOLVED_YET:/poll/<id> while the job waits in queue, the poll answers ? while a solver is working on it, and the token text when it is solved. Poll no faster than once every three seconds.

For reCAPTCHA v3 the same call takes type=5 plus action and min_score inside the token parameters — see the API reference for the full field list. A Turnstile widget is the same loop with type=12 and sitekey instead of googlekey, as the Turnstile type page shows.

Step two: inject the token and submit

The response field lives in the main document — only the widget's visual part is inside the iframe — so execute_script reaches it without switching frames:

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

options = Options()
options.add_argument("--headless=new")
options.add_argument("--lang=en-US")

driver = webdriver.Chrome(options=options)
driver.get("https://www.example.com/signup")

driver.execute_script(
    """
    const field = document.querySelector(
        '[name="g-recaptcha-response"]');
    if (field) {
        field.value = arguments[0];
        field.dispatchEvent(new Event('input', {bubbles: true}));
        field.dispatchEvent(new Event('change', {bubbles: true}));
    }
    """,
    token,
)

WebDriverWait(driver, 10).until(
    EC.element_to_be_clickable((By.CSS_SELECTOR, 'button[type="submit"]'))
).click()
WebDriverWait(driver, 10).until(EC.url_contains("dashboard"))
driver.quit()

Two Selenium-specific details matter here. First, inject and click in one flow: forms often re-render after the token lands, and a stale reference between the two steps is the most common StaleElementReferenceException in this pattern — the explicit wait above re-finds the button at click time. Second, inject right before submitting: the token is single-use and expires in about two minutes, so solving first and submitting an hour later is a guaranteed rejection.

When the page submits with fetch instead of a form

SPA-style pages read the response field in JavaScript and send it with fetch(). For Chrome you can wire the token onto the wire before the page even loads, with the DevTools protocol:

driver.execute_cdp_cmd("Page.addScriptToEvaluateOnNewDocument", {
    "source": """
        window.__ec_token = null;
        const origFetch = window.fetch;
        window.fetch = function (...args) {
            const init = args[1] || {};
            if (window.__ec_token && typeof init.body === 'string') {
                init.body += '&g-recaptcha-response=' + window.__ec_token;
                args[1] = init;
            }
            return origFetch.apply(this, args);
        };
    """,
})

driver.get("https://www.example.com/signup")
driver.execute_script("window.__ec_token = arguments[0];", token)
driver.find_element(By.CSS_SELECTOR, 'button[type="submit"]').click()

The patch runs on every document of that driver session, reads the token you store on window after the solve, and appends it only to request bodies — the page's own code never knows the difference.

The same flow in Node.js

The Node client in the zip speaks the identical contract; the example example_recaptchav2.js that ships with the client is the shape you copy. Driving the page with selenium-webdriver:

const { Builder, By } = require('selenium-webdriver');
const HttpClient = require('./lib/endcaptcha.js');

async function main() {
  const pageurl = 'https://www.example.com/signup';
  const client = new HttpClient(process.env.EC_USER, process.env.EC_PASS);
  const driver = await new Builder().forBrowser('chrome').build();

  const job = await new Promise((resolve) => {
    client.decode({
      type: 4,
      googlekey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI',
      pageurl,
    }).on('response', resolve);
  });
  if (job.error) throw new Error(job.error);

  await driver.get(pageurl);
  await driver.executeScript(`
    const field = document.querySelector('[name="g-recaptcha-response"]');
    field.value = arguments[0];
    field.dispatchEvent(new Event('change', {bubbles: true}));
  `, job.text);

  await driver.findElement(By.css('button[type="submit"]')).click();
  await driver.quit();
}

main().catch((err) => { console.error(err); process.exit(1); });

The injection code does not care which language produced the token; keep the solve as a plain function call in whatever language the automation already lives in.

pageurl, proxies and the session you submit from

Send the full URL of the page that hosts the widget, path included: https://www.example.com/signup, never the domain root, and the same URL the driver actually loads — driver.current_url is the reference. If the solving address and the submitting address differ, pages reject the answer: run the driver through a proxy with --proxy-server and pass proxy and proxytype inside the token parameters so the solver comes from the same address you submit from. The fields are documented in the Turnstile and Amazon WAF guides, which follow the same shape.

On Selenium Grid nothing changes: the token is produced off the node, so any grid node can inject it — keep the pageurl pointing at the public URL of the page, not at the node's address. Keep the browser fingerprint stable between attempts too: the same headless flags, the same user agent, the same proxy. A run that works headed and fails headless is almost always a fingerprint change between the solve and the submit, not a problem with the API.

Troubleshooting

Answer from the API or the driver What it means What to do
UNSOLVED_YET:/poll/<id> Your job is in queue Keep polling, no faster than every 3 seconds
? A solver is working on it Keep polling; the job expires if abandoned for too long
ERROR:NOT A VALID CAPTCHA A field failed validation Check googlekey/sitekey and the pageurl format
ERROR:UNSUCCESSFUL UPLOAD The upload itself failed Credentials, balance, or a malformed token payload
Token from the API, rejection from the page The pageurl does not match driver.current_url, or the token was reused Match the URL exactly; tokens are single-use and expire in about two minutes
StaleElementReferenceException between inject and click The form re-rendered after the token landed Re-find the element; inject and click inside one explicit wait
Works headed, fails headless The fingerprint changed between attempts Keep flags, user agent and proxy identical across the run

Questions developers ask

Which CAPTCHA types can I solve this way? reCAPTCHA v2 (type=4), reCAPTCHA v3 (type=5) and Cloudflare Turnstile (type=12) all follow this loop with different parameters — see the v2 type page, the v3 type page and the Turnstile type page for the field lists.

Does it work with Selenium Grid? Yes — the token is produced outside the browser, so any node can inject it. Keep the pageurl on the public URL of the page.

How long does a solve take? The service publishes a seven-second SLA; in practice the poll returns in seconds when the queue is free, and decode() caps the wait for you.

Do I need a proxy? Only when the site cares about the address: if the driver submits from one address, solve from the same one by passing proxy and proxytype with the token parameters.

Is this different from the Playwright flow? Only the injection API — the solve step is identical. The Playwright guide covers the same token loop for Playwright scripts, and our Selenium handling guide covers driver-side patterns around challenges.

Where do I start? The quick start shows the same flow with cURL, and creating an account takes a minute.

Back to all articles

Sign Up

Create Account

Already have an account? please
Login
Terms Of Services

Terms of Services

EndCaptcha Terms of Service:

EndCaptcha is a Data Entry Company that focuses on fast and reliable conversion of Images to Text and general digitization. Any use of the service for activities that are deemed illegal is strictly prohibited.

EndCaptcha is not responsible for the actions of its clients. Any complaint about any particular EndCaptcha client should be reported to [email protected]. EndCaptcha reserves the right to suspend its clients' account if, through an [email protected] report, gets hold of undeniable proof that the client is engaging in illicit activities.

EndCaptcha reserves the right to change at any time its Terms of Service, prices of packages, Service Level Agreement conditions and compensation, and Unsupported Images without prior notice.

Unsupported Activities:

EndCaptcha reserves the right to terminate any client account without notice if, by its sole judgement, EndCaptcha determines that the client is engaged in activities that negatively affect service levels. Examples of these harmful activities are:
- Using EndCaptcha's API in ways that negatively affects the operation of the service
- Constantly submitting images that are unreadable
- Exploiting in any way the Service Level Agreement compensation
- Exploiting the service function used to claim images as incorrectly solved

Ownership of an EndCaptcha account:

All accounts created in EndCaptcha remain as sole property of EndCaptcha, and its parent company. Sharing account details, selling and reselling accounts, and selling awarded coupons is prohibited. Violation of this policy will result in account suspension.

Service Level Agreement:

Even though customers may submit Unsupported Images, and EndCaptcha will do it’s best effort to solve said images in a timely fashion, the Service Level Agreement won't cover any Unsupported Images and won't be available during dates included in the Maintenance and Holidays Calendar. EndCaptcha reserves the right to include Unsupported Images at any time without previous notice.

Additional information about the types of insurances provided in the Service Level Agreement may be found here.

Refunds and Chargebacks:

Any fulfilled claims for chargebacks before consulting our helpdesk will result in an immediate suspension of the clients account and appropriate legal action (fraud).

Refunds are processed in a case-by-case basis. We will not award refunds if:
- The client is unsatisfied with the results obtained when submitting Unsupported Images.
- The client has partially used the service and has no use for remaining balance in the client's account.
- The client was unaware of any explicitly written service constraints before making a purchase.

If a refund is awarded, the client will receive funds equivalent to the remaining credit balance in the client's account.

Maintenance and Holidays Calendar

Maintenance and Holidays Calendar

Although we will try our best to keep maintenance and undercapacity occurrences to a minimum, there will be rare occasions in which these events will be unavoidable, and thus not covered by the Insurances of our Service Level Agreement. The Maintenance and Holidays Calendar will show the foreseeable events of diminished Quality of Service.

Event Description Date Duration
Unsupported Images

Unsupported Images

The following images can be solved by our team of workers, but are not insured by the Slowness Insurance. These images are usually unreadable, math problems, or have a lengthy amount of characters. The same exception applies for the Recaptcha V2 challenges. Due to their complexity, are not eligible for Slowness Insurance


Recaptcha V2 Fast Solving Service Recaptcha Invisible Decoding Solution
Refunds and Chargebacks

Refunds and Chargebacks

Any fulfilled claims for chargebacks before consulting our helpdesk will result in an immediate suspension of the clients account and appropriate legal action (fraud).

Refunds are processed in a case-by-case basis. We will not award refunds if:
- The client is unsatisfied with the results obtained when submitting Unsupported Images.
- The client has partially used the service and has no use for remaining balance in the client's account.
- The client was unaware of any explicitly written service constraints before making a purchase.

If a refund is awarded, the client will receive funds equivalent to the remaining credit balance in the client's account.

Service Level Agreement

Service Level Agreement

EndCaptcha offers a unique Service Level Agreement that compensates its users when there is a drop in Quality Standards.

About Compensation

Users will be compensated with Compensatory Credits, which will be deposited once the currently used Credit Package has been consumed. The amount of Compensatory Credits will be calculated depending on the type of Insurance and the Compensation Coverage Level of the currently used package.

Compensatory Credits, Promotional Credits, and Free Credits won't be subject to the terms of the Service Level Agreement and its insurances.

Compensation Coverage Level

The user will be given a Slowness Insurance and an Outage Insurance. These types of insurances will provide compensation to users whenever certain Quality Standards are not met. There will be different Compensation Coverage Levels depending on what Credit Package was bought. The amount of Compensatory Credits will be the result of multiplying the Compensation Coverage Level percentage with the amount of quantifiable times the Quality Standards are not met.

Slowness Insurance

*Approximately 70% of all images are decoded with-in 7-8 seconds on average. The remainder is also guaranteed to be decoded with-in 11 seconds max. For any images that take longer than 11 seconds to solve, our slowness insurance policies kick in.

The Slowness Insurance will provide compensation to EndCaptcha users for images that are getting typed in more than 11 seconds. The compensation will be calculated on an Image-by-image basis.

Example Case:
Compensation Level: 60% Coverage
Images over 11 seconds: 2,000
Compensation Credits: 1,200

The Slowness Insurance won't cover Unsupported Images, and is not applicable during the time ranges included in the Maintenance and Holidays Calendar.

Outage Insurance

The Outage Insurance will provide compensation to EndCaptcha users when the service suffers from an unexpected outage that renders the service partially or completely unresponsive. The Outage Insurance will only cover cases of registered server problems on EndCaptcha's side, it will not cover issues in the user's side or connectivity problems between the user and EndCaptcha.

The Outage Insurance will only compensate credits in accordance with the usual activity of the users during the event. The usual activity of the user will be determined by the average past submissions made by the user during the weekday and time of the outage event.

Example Case:
Compensation Level: 100% Coverage
Usual Activity During Event: 2,000 Images
Compensation Credits: 2,000 Images

The Outage Insurance will be evaluated on a case by case basis whenever the user opens an outage claim at [email protected]. For outage claims to be valid, they have to be opened with-in 72 hours of the outage occurring.

The Outage Insurance is not applicable during the Maintenance time ranges included in the Maintenance and Holidays Calendar, which have been announced a minimum of 12 hours in advance. The Outage Insurance is not applicable when deliberate attacks to our service are made.

EndCaptcha reserves the right to change its Service Level Agreement without prior notice.