Logo of «2Captcha»To home page
Captcha bypass tutorials

Was this helpful?

How to bypass Cloudflare Turnstile on StockX

Gregory Fisher
Gregory Fisher

Technical engineer

The login and signup page on StockX (accounts.stockx.com) is protected by Cloudflare Turnstile. The main quirk of this particular implementation is that the page URL carries a long, dynamic state parameter that changes with every new session or login attempt.

For a solve to actually work, it's critical to send the API the exact URL the captcha was triggered on, together with the static websiteKey. This guide walks through extracting the parameters, sending the task, and injecting the resulting token.

Task parameters

Solving Cloudflare Turnstile here uses the TurnstileTaskProxyless task type.

Parameter Type Required Description
type String Yes Must be set to TurnstileTaskProxyless
websiteURL String Yes The full URL of the page where the captcha appears (including the dynamic state parameter)
websiteKey String Yes The static site key (starts with 0x4...)
action String No Optional action parameter, if the site uses one

Finding the parameters

  1. Open the StockX login page: https://accounts.stockx.com/u/login/identifier
  2. Press F12 to open DevTools and switch to the Network tab
  3. Turn on Preserve log, since the page may reload or refresh the URL
  4. Enter any data into the form and click the continue button (for example, Continue) to trigger the captcha
  5. In the Network tab filter, type turnstile or challenges.cloudflare.com
  6. Find the request going to Cloudflare's API. In that request's parameters, or in the page source (search for data-sitekey), you'll find the websiteKey value (for example, 0x4AAAAAACg7RNT7No-kJ-Ms)
  7. Important: copy the full URL from the browser's address bar at the moment the captcha is active. It will look something like this:
Copy
https://accounts.stockx.com/u/login/identifier?state=hKFo2SBwV1NKQThCSU1MbHd1bTJRODdkYm03NkFjaTluMWY2cKFur3VuaXZlcnNhbC1sb2dpbqN0aWTZIHFvRmxVcGN6Sk52RmNqbmQxS3JHUEk3d2ZwaGdhZWR3o2NpZNkgT1Z4cnQ0VkpxVHg3TElVS2Q2NjFXMER1Vk1wY0ZCeUQ&ui_locales=en

Warning: using a URL without the state parameter, or with an outdated one, will cause the solve to fail, since Turnstile strictly checks that the URL matches the session.

Code examples

Python + requests

An example of sending the task and polling for the result using the requests library.

python Copy
import requests
import time

API_KEY = 'YOUR_API_KEY'
# The URL must be copied in full, including the long state parameter
WEBSITE_URL = 'https://accounts.stockx.com/u/login/identifier?state=hKFo2SBwV1NKQThCSU1MbHd1bTJRODdkYm03NkFjaTluMWY2cKFur3VuaXZlcnNhbC1sb2dpbqN0aWTZIHFvRmxVcGN6Sk52RmNqbmQxS3JHUEk3d2ZwaGdhZWR3o2NpZNkgT1Z4cnQ0VkpxVHg3TElVS2Q2NjFXMER1Vk1wY0ZCeUQ&ui_locales=en'
WEBSITE_KEY = '0x4AAAAAACg7RNT7No-kJ-Ms'

# 1. Send the task
url = 'https://api.2captcha.com/createTask'
headers = {
    'Content-Type': 'application/json',
    'X-API-Key': API_KEY
}
payload = {
    "clientKey": API_KEY,
    "task": {
        "type": "TurnstileTaskProxyless",
        "websiteURL": WEBSITE_URL,
        "websiteKey": WEBSITE_KEY
    }
}

response = requests.post(url, headers=headers, json=payload)
result = response.json()

if result.get('errorId') == 0:
    task_id = result.get('taskId')
    print(f"Task created, ID: {task_id}")

    # 2. Wait for the solution
    res_url = 'https://api.2captcha.com/getTaskResult'
    while True:
        time.sleep(5)  # Pause between requests

        res_payload = {
            "clientKey": API_KEY,
            "taskId": task_id
        }

        res_response = requests.post(res_url, headers=headers, json=res_payload).json()

        if res_response.get('errorId') == 0 and res_response.get('status') == 'ready':
            print("Captcha solved successfully!")
            token = res_response['solution']['token']
            print(f"Token: {token}")
            break
        elif res_response.get('errorId') != 0:
            print(f"Error getting result: {res_response.get('errorDescription')}")
            break
else:
    print(f"Error creating task: {result.get('errorDescription')}")

Python + Playwright

An automation example that dynamically extracts the current URL and key, sends the task, and retrieves the token.

python Copy
from playwright.sync_api import sync_playwright
import requests
import time

API_KEY = 'YOUR_API_KEY'
TARGET_URL = 'https://accounts.stockx.com/u/login/identifier'

def solve_stockx_turnstile():
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()

        try:
            page.goto(TARGET_URL)

            # An action that triggers the captcha needs to happen here
            # For example, entering an email and clicking Continue
            # page.fill('input[type="email"]', 'test@example.com')
            # page.click('button[type="submit"]')

            # Wait for the Turnstile widget to appear, or for the URL to update
            page.wait_for_selector('iframe[src*="challenges.cloudflare.com"]', timeout=10000)

            # Extract the current URL (with the dynamic state) and websiteKey
            current_url = page.url
            website_key = page.evaluate("""
                () => {
                    const iframe = document.querySelector('iframe[src*="challenges.cloudflare.com"]');
                    if (iframe) {
                        const src = new URL(iframe.src);
                        return src.searchParams.get('sitekey');
                    }
                    return null;
                }
            """)

            if not website_key:
                # Fall back to checking data-sitekey directly
                website_key = page.evaluate("document.querySelector('[data-sitekey]')?.getAttribute('data-sitekey')")

            print(f"URL: {current_url}")
            print(f"Key: {website_key}")

            # Send to the API
            api_url = 'https://api.2captcha.com/createTask'
            headers = {'Content-Type': 'application/json', 'X-API-Key': API_KEY}
            payload = {
                "clientKey": API_KEY,
                "task": {
                    "type": "TurnstileTaskProxyless",
                    "websiteURL": current_url,
                    "websiteKey": website_key
                }
            }

            task_response = requests.post(api_url, headers=headers, json=payload).json()
            task_id = task_response.get('taskId')

            # Wait for the solution
            res_url = 'https://api.2captcha.com/getTaskResult'
            token = None
            for _ in range(20):  # Wait up to ~100 seconds
                time.sleep(5)
                res = requests.post(res_url, headers=headers, json={"clientKey": API_KEY, "taskId": task_id}).json()
                if res.get('status') == 'ready':
                    token = res['solution']['token']
                    break
                if res.get('errorId') != 0:
                    print(f"API error: {res.get('errorDescription')}")
                    break

            if token:
                print(f"Token received: {token[:30]}...")
                # Move on to injecting the token (see the section below)
            else:
                print("Failed to get a token.")

        except Exception as e:
            print(f"Error: {e}")
        finally:
            pass  # browser.close() — uncomment to close the browser

if __name__ == '__main__':
    solve_stockx_turnstile()

How to use the returned token (injection options)

Since the exact token-handling mechanism on StockX may change, here are the most common ways Turnstile tokens get injected. You'll need to check the page source (Elements tab) to figure out which one applies.

Option 1: hidden form field (most common). The site expects the token in a hidden input named cf-turnstile-response (or similar) inside the form.

python Copy
page.evaluate(f"""
    let input = document.querySelector('input[name="cf-turnstile-response"]');
    if (!input) {{
        input = document.createElement('input');
        input.type = 'hidden';
        input.name = 'cf-turnstile-response';
        document.querySelector('form').appendChild(input);
    }}
    input.value = "{token}";
""")

Option 2: JavaScript callback. The widget was initialized with a data-callback parameter. In this case, call that function manually with the token.

python Copy
# Replace 'turnstileCallback' with the actual function name from the data-callback attribute
page.evaluate(f"""
    if (typeof turnstileCallback === 'function') {{
        turnstileCallback("{token}");
    }}
""")

Option 3: global Turnstile object. Some sites read the token from a global object, or expect the widget's state to be updated explicitly.

python Copy
page.evaluate(f"""
    // Try to find the widget instance and set the token
    if (window.turnstile) {{
        // Some implementations require calling reset or setting a value manually
        console.log("Turnstile instance found, token ready for injection");
    }}
    // Force-set the value if the site uses custom logic
    window.CF_TURNSTILE_TOKEN = "{token}";
""")

Testing in sandbox mode

Before running the automation repeatedly, verify that the extracted parameters are correct using 2Captcha's sandbox.

  1. Submit the task through the API using your websiteURL and websiteKey
  2. Go to the sandbox and switch to Worker mode
  3. If you pick up that task and see the Turnstile widget loading correctly and can solve it, the parameters were collected correctly. If the widget throws an error (for example, Invalid domain), check that the websiteURL was copied in full, including the state parameter

Common errors and fixes

Error / issue Cause Fix
ERROR_INVALID_SITE_KEY Wrong websiteKey or a domain mismatch Make sure the key starts with 0x4 and was copied without extra spaces
No solution ever arrives, CAPCHA_NOT_READY indefinitely Wrong or stale websiteURL Make sure you're sending the URL together with the long state parameter that was current at the time of the request
Form doesn't submit after the token is injected Token was placed in the wrong field, or the callback wasn't called Check the Network tab while submitting the form manually. Find the request going to the server and see exactly which field (or header) carries the Turnstile token
ERROR_PROXY_* (if using a non-proxyless task) Turnstile on StockX typically doesn't need a proxy if you're solving it in the same environment you're working from Use TurnstileTaskProxyless. If the site is blocking your IP, configure the proxy at the Playwright/Selenium level, not in the API task