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

Was this helpful?

How to bypass captcha on SHEIN

Gregory Fisher
Gregory Fisher

Technical engineer

The SHEIN website (shein.com) uses its own custom bot protection system, which does not rely on third-party solutions like reCAPTCHA or hCaptcha. When suspicious activity is detected, the visitor is redirected to an internal risk gateway at /risk/challenge.

The main feature of this captcha is that there is no sitekey or token that can simply be injected into a form. The puzzle is solved directly on the page using mouse clicks, which requires a real browser (such as Playwright, Puppeteer, or Selenium). In this guide, we will break down how to identify the puzzle type, take the correct screenshots, send them to the API, and programmatically click the answers.

Task Parameters (API v2)

Depending on the type of puzzle displayed on the page, different task types are used when sending requests to the createTask endpoint.

For Icon Sequence (CoordinatesTask)

Parameter Type Required Description
clientKey String Yes Your API key
type String Yes CoordinatesTask
body String Yes Screenshot of the main image in Base64 format (without the data:image/png;base64, prefix)
imgInstructions String Yes Screenshot of the icon strip (hints) in Base64 format
comment String Yes Text instruction for the worker, e.g., Click the icons shown in the instruction, in the same order, left to right

For 3x3 Grid (GridTask)

Parameter Type Required Description
clientKey String Yes Your API key
type String Yes GridTask
body String Yes Screenshot of the entire 3x3 grid in Base64 format
rows Integer Yes Number of rows (always 3)
columns Integer Yes Number of columns (always 3)
imgInstructions String Yes Screenshot of the sample icon in Base64 format
comment String Yes Text instruction, e.g., Select exactly 3 images that show the same action or object as the small icon

How to identify the captcha step on the page

Since there is no sitekey, your task is to determine which verification step the user is currently on.

  1. Open Developer Tools (F12) and go to the Network tab.
  2. If the page URL contains /risk/challenge, you are on the verification gateway.
  3. Go to the Elements tab and analyze the DOM:
    • Checkbox: look for an element with the text I am human.
    • Icon sequence: look for the container .captcha_click_wrapper with elements .pic_wrapper (main image) and .pic_elg_wrapper (icon strip).
    • 3x3 grid: look for the web component <nine-captcha-custom>. The tiles are located inside its Shadow DOM and have the class .nine-content-img.

Code Examples

Python + requests (API v2)

Example of sending a CoordinatesTask or GridTask and receiving the result.

python Copy
import requests
import time
import base64
import os

API_KEY = os.environ.get("APIKEY", "YOUR_API_KEY")

def solve_captcha_task(task_payload: dict, timeout: int = 180) -> dict:
    url = "https://2captcha.com/createTask"
    headers = {"Content-Type": "application/json"}
    payload = {"clientKey": API_KEY, "task": task_payload}
    
    response = requests.post(url, headers=headers, json=payload).json()
    if response.get("errorId") != 0:
        raise RuntimeError(f"Task creation error: {response.get('errorDescription')}")
        
    task_id = response["taskId"]
    print(f"Task created, ID: {task_id}")
    
    res_url = "https://2captcha.com/getTaskResult"
    deadline = time.time() + timeout
    
    while time.time() < deadline:
        time.sleep(5)
        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:
            raise RuntimeError(f"Result retrieval error: {res_response.get('errorDescription')}")
            
        if res_response.get("status") == "ready":
            return res_response["solution"]
            
    raise TimeoutError("Captcha solving timeout")

# Example usage for CoordinatesTask:
# task = {
#     "type": "CoordinatesTask",
#     "body": "iVBORw0KGgoAAAANSUhEUg...", # Base64 of the main image
#     "imgInstructions": "iVBORw0KGgoAAAANSUhEUg...", # Base64 of the icon strip
#     "comment": "Click the icons shown in the instruction, in the same order, left to right"
# }
# solution = solve_captcha_task(task)
# print(solution) # Returns {"coordinates": [{"x": 118, "y": 402}, ...]}

Python + Playwright (Full solving cycle)

Example of automation that identifies the captcha type, takes screenshots, sends the task to the API, and clicks the result.

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

API_KEY = os.environ.get("APIKEY", "YOUR_API_KEY")
TARGET_URL = "https://us.shein.com/pdsearch/dress/"

# JavaScript to determine the current captcha step and get element coordinates
STATE_JS = """
() => {
  const roots = [];
  const collect = (root) => {
    roots.push(root);
    for (const el of root.querySelectorAll('*')) if (el.shadowRoot) collect(el.shadowRoot);
  };
  collect(document);
  
  const visible = (e) => {
    if (!e) return false;
    const r = e.getBoundingClientRect(), s = getComputedStyle(e);
    return r.width > 0 && r.height > 0 && s.display !== 'none' && s.visibility !== 'hidden';
  };
  
  const qa = (sel) => roots.flatMap((r) => [...r.querySelectorAll(sel)]);
  const rect = (e) => { const r = e.getBoundingClientRect(); return [r.x, r.y, r.width, r.height]; };
  const first = (sel) => { const e = qa(sel).find(visible); return e ? rect(e) : null; };
  
  const pic = qa('.captcha_click_wrapper .pic_wrapper').find(visible);
  if (pic) {
    return {
      stage: 'icon_click', 
      image: rect(pic),
      icons: first('.captcha_click_wrapper .pic_elg_wrapper'),
      confirm: first('.captcha_click_confirm'), 
      refresh: first('.captcha_click_refresh')
    };
  }
  
  const tiles = qa('.nine-content-img').filter(visible);
  if (tiles.length) {
    return {
      stage: 'nine_captcha', 
      tiles: tiles.map(rect),
      icon: first('.header-content-img'), 
      refresh: first('.nine-refresh')
    };
  }
  
  for (const r of roots) {
    for (const e of r.querySelectorAll('*')) {
      if (e.childElementCount === 0 && e.textContent.trim() === 'I am human' && visible(e)) {
        return {stage: 'one_pass', checkbox: rect(e)};
      }
    }
  }
  return {stage: 'none'};
}
"""

def send_task(task_data):
    url = "https://2captcha.com/createTask"
    res = requests.post(url, json={"clientKey": API_KEY, "task": task_data}).json()
    if res.get("errorId") != 0:
        raise Exception(f"Task error: {res.get('errorDescription')}")
    
    task_id = res["taskId"]
    for _ in range(30):
        time.sleep(5)
        r = requests.post("https://2captcha.com/getTaskResult", json={"clientKey": API_KEY, "taskId": task_id}).json()
        if r.get("status") == "ready":
            return r["solution"]
    raise TimeoutError("Captcha solving timeout")

def solve_shein_challenge(page, max_rounds=5):
    if "/risk/challenge" not in page.url:
        return True
        
    page.wait_for_function(f"({STATE_JS})().stage !== 'none'", timeout=15000)
    
    for round_num in range(1, max_rounds + 1):
        time.sleep(1.5) # Give time for new images to load
        state = page.evaluate(STATE_JS)
        
        if state["stage"] == "one_pass":
            label = state["checkbox"]
            page.mouse.click(label[0] - 20, label[1] + label[3] / 2)
            time.sleep(3)
            if "/risk/challenge" not in page.url:
                return True
                
        elif state["stage"] == "icon_click":
            image_png = page.screenshot(clip={"x": state["image"][0], "y": state["image"][1], "width": state["image"][2], "height": state["image"][3]})
            icons_png = page.screenshot(clip={"x": state["icons"][0], "y": state["icons"][1], "width": state["icons"][2], "height": state["icons"][3]})
            
            solution = send_task({
                "type": "CoordinatesTask",
                "body": base64.b64encode(image_png).decode(),
                "imgInstructions": base64.b64encode(icons_png).decode(),
                "comment": "Click the icons shown in the instruction, in the same order, left to right"
            })
            
            points = solution.get("coordinates", [])
            if len(points) < 2:
                page.mouse.click(state["refresh"][0] + state["refresh"][2]/2, state["refresh"][1] + state["refresh"][3]/2)
                continue
                
            # Recalculate coordinates accounting for device pixel ratio
            # (Simplified here; in production, parse actual PNG dimensions)
            x0, y0 = state["image"][0], state["image"][1]
            for p in points:
                page.mouse.click(x0 + p["x"], y0 + p["y"])
                time.sleep(0.4)
            page.mouse.click(state["confirm"][0] + state["confirm"][2]/2, state["confirm"][1] + state["confirm"][3]/2)
            
        elif state["stage"] == "nine_captcha":
            tiles = sorted(state["tiles"], key=lambda t: (round(t[1] / t[3]), t[0]))
            x0, y0 = min(t[0] for t in tiles), min(t[1] for t in tiles)
            x1, y1 = max(t[0] + t[2] for t in tiles), max(t[1] + t[3] for t in tiles)
            
            grid_png = page.screenshot(clip={"x": x0, "y": y0, "width": x1 - x0, "height": y1 - y0})
            icon_png = page.screenshot(clip={"x": state["icon"][0], "y": state["icon"][1], "width": state["icon"][2], "height": state["icon"][3]})
            
            solution = send_task({
                "type": "GridTask",
                "body": base64.b64encode(grid_png).decode(),
                "rows": 3, "columns": 3,
                "imgInstructions": base64.b64encode(icon_png).decode(),
                "comment": "Select exactly 3 images that show the same action or object as the small icon"
            })
            
            picks = [n for n in solution.get("click", []) if 1 <= n <= 9]
            if len(picks) != 3:
                page.mouse.click(state["refresh"][0] + state["refresh"][2]/2, state["refresh"][1] + state["refresh"][3]/2)
                continue
                
            for n in picks:
                t = tiles[n - 1]
                page.mouse.click(t[0] + t[2]/2, t[1] + t[3]/2)
                time.sleep(0.4)
                
        try:
            page.wait_for_url(lambda url: "/risk/challenge" not in url, timeout=8000)
            print(f"Captcha successfully passed in round {round_num}")
            return True
        except Exception:
            print(f"Round {round_num} not accepted, trying a new puzzle")
            
    return False

with sync_playwright() as p:
    browser = p.chromium.launch(headless=False)
    page = browser.new_page()
    page.goto(TARGET_URL)
    
    if solve_shein_challenge(page):
        page.wait_for_load_state("domcontentloaded")
        print("Successful entry:", page.title())
        
    browser.close()

How to use the received result

Since SHEIN does not use tokens, the solving result is used for direct interaction with the page:

  1. For the icon sequence (CoordinatesTask): The API returns an array of coordinates [{"x": 118, "y": 402}, ...]. Click strictly in this order. Important: the coordinates are given in the pixels of the submitted image. If you take a screenshot on a HiDPI screen, divide x and y by the devicePixelRatio before adding the element's offset on the page. After all clicks, press the Confirm button.
  2. For the 3x3 grid (GridTask): The API returns tile numbers from 1 to 9 (e.g., [2, 5, 9]). Tiles are numbered left to right, top to bottom. Click the centers of the corresponding tiles. There is no Confirm button here; the widget submits the answer automatically after the third click.
  3. If SHEIN rejects the answer (returns code=9001 System error), do not report it as an incorrect solution immediately. This is a quirk of SHEIN's risk assessment, not a worker error. Simply refresh the puzzle and try again. Plan for 2-3 rounds in your script logic.

Alternative Solving Methods

Besides the direct API, there are two additional ways to bypass the captcha on SHEIN that can be useful in various automation scenarios.

1. Browser Extension (Captcha Bypass Extension)

If you work manually or use Selenium/Playwright automation with a real browser, you can install our special captcha bypass extension.

Advantages:

  • No need to write complex code for taking screenshots and sending requests
  • Automatically detects the appearance of the puzzle and solves it in the background
  • Works with most popular captcha types, including custom SHEIN tasks
  • Ideal for manual browsing or complex automation scenarios

More about the extension: https://2captcha.com/captcha-bypass-extension

2. Browser API (Scraper)

For complex parsing tasks where you need not only to bypass the captcha but also extract product data after successful verification, we offer Browser API — a cloud solution based on headless browsers.

Advantages:

  • Fully managed cloud browser with built-in captcha bypass support
  • No need to configure proxies, User-Agent, or fight against blocks
  • Built-in data extraction functions (screenshots, HTML, JavaScript execution)
  • Automatic captcha solving when navigating the site
  • Ideal for mass parsing and data collection

More about Browser API: https://2captcha.com/scraper/browser-api/dashboard

Common Errors and Solutions

Error / Problem Cause Solution
Clicks miss the image Coordinates were not recalculated from screenshot pixels to CSS pixels Divide the coordinates by the ratio of the screenshot size to the element size (account for devicePixelRatio).
Grid tiles are not found They are inside the Shadow DOM of the <nine-captcha-custom> element Use recursive traversal of all shadowRoots, as shown in the STATE_JS example.
Worker receives a blank image Screenshot was taken before the new sprite loaded after a refresh Wait 1-2 seconds after clicking the refresh button before taking screenshots.
Correct answer is rejected (code=9001) SHEIN risk assessment, not a worker error Repeat with the next puzzle. Plan for 2-3 rounds in your script logic.
All answers are rejected SHEIN has flagged the browser profile or IP as suspicious Change the browser profile and use residential proxies. Additional rounds will not help.
ERROR_ZERO_BALANCE or ERROR_KEY_DOES_NOT_EXIST Account or key issue Stop retries, top up your balance, or verify your API key.