Was this helpful?
How to bypass Cloudflare Turnstile on StockX
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
- Open the StockX login page: https://accounts.stockx.com/u/login/identifier
- Press F12 to open DevTools and switch to the Network tab
- Turn on Preserve log, since the page may reload or refresh the URL
- Enter any data into the form and click the continue button (for example, Continue) to trigger the captcha
- In the Network tab filter, type turnstile or challenges.cloudflare.com
- 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)
- Important: copy the full URL from the browser's address bar at the moment the captcha is active. It will look something like this:
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
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
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
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
# 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
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.
- Submit the task through the API using your websiteURL and websiteKey
- Go to the sandbox and switch to Worker mode
- 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 |
Useful links
- Sandbox for testing the API: https://2captcha.com/setting#sandbox
- API documentation for Cloudflare Turnstile: https://2captcha.com/api-docs/cloudflare-turnstile
- Python SDK on GitHub: https://github.com/2captcha/2captcha-python
- More site-specific captcha bypass examples: https://2captcha.com/h?category=sites