Developer Tutorial10 min readArkose Labs & FunCaptcha

How to Solve Arkose Labs FunCaptcha in Python: Playwright & 2Captcha Guide

A complete developer guide on solving and bypassing Arkose Labs FunCaptcha challenges (3D rotation, animal matching, dice counts) in Python with Playwright and the 2Captcha API. Includes public key capture, blob extraction, and token injection.

1. How Arkose Labs FunCaptcha Operates

Arkose Labs (formerly FunCaptcha) is the primary anti-bot defense guarding enterprise platforms including Twitter / X, OpenAI / ChatGPT, Microsoft Outlook, LinkedIn, and Roblox. Its verification puzzles feature:

  • 3D Animal & Object Rotation: Users must rotate a 3D animal model until it faces in the direction specified by an arrow icon or hand.
  • Pattern & Coordinate Matching: Matching dice numbers, identifying shadows, or selecting pairs of identical items across 5 to 10 sequential rounds.
  • Encrypted Data Blobs (data[blob]): High-risk platforms generate an ephemeral cryptographic session blob that ties the CAPTCHA solution directly to the client session.

Because standard computer vision algorithms struggle with 3D rotation perspectives, 2Captcha uses a hybrid system combining specialized computer vision models with real-time human verification, achieving an industry-leading 99.2% pass rate.

2. Installing Required Dependencies

Install the official 2captcha-python library and playwright:

pip install 2captcha-python playwright
playwright install chromium

3. Basic Task Dispatch via 2Captcha SDK

The TwoCaptcha SDK provides native support for FunCaptcha parameters:

import os
from twocaptcha import TwoCaptcha

api_key = os.getenv("TWOCAPTCHA_API_KEY")
solver = TwoCaptcha(api_key)

try:
    print("Dispatching FunCaptcha task to 2Captcha...")
    result = solver.funcaptcha(
        sitekey="2CB16598-CB82-4CF7-B332-5DE690AE6F0F", # Public Key (pk)
        url="https://target-portal.com/signup",
        surl="https://client-api.arkoselabs.com",
        # Optional data blob required by Twitter/X, OpenAI, etc.:
        # data={"blob": "captured_blob_hex_value"}
    )
    token = result["code"]
    print("FunCaptcha solved successfully!")
    print(f"Token: {token[:45]}...")
except Exception as e:
    print(f"Failed to solve FunCaptcha: {e}")

4. Complete Playwright Automation Pipeline

The production script below uses Playwright network listeners to intercept the Arkose API handshake, extract the public key and session blob, dispatch the puzzle to 2Captcha, and populate input[name="fc-token"]:

import asyncio
import os
import re
from playwright.async_api import async_playwright
from twocaptcha import TwoCaptcha

API_KEY = os.getenv("TWOCAPTCHA_API_KEY")
solver = TwoCaptcha(API_KEY)
TARGET_URL = "https://example-portal.com/login"

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=True)
        context = await browser.new_context()
        page = await context.new_page()

        captured_blob = None
        captured_pk = None

        # Listen to Arkose API network requests to extract public key and data blob
        async def handle_request(request):
            nonlocal captured_blob, captured_pk
            if "arkoselabs.com" in request.url:
                pk_match = re.search(r"[?&]pk=([A-Fa-f0-9-]+)", request.url)
                if pk_match:
                    captured_pk = pk_match.group(1)
                post_data = request.post_data
                if post_data and "data%5Bblob%5D=" in post_data:
                    blob_match = re.search(r"data%5Bblob%5D=([^&]+)", post_data)
                    if blob_match:
                        captured_blob = blob_match.group(1)

        page.on("request", handle_request)

        print(f"Navigating to {TARGET_URL}...")
        await page.goto(TARGET_URL, wait_until="networkidle")

        # Wait for FunCaptcha iframe or container
        await page.wait_for_selector('iframe[src*="arkoselabs.com"], #fc-token, input[name="fc-token"]', timeout=12000)

        # Fallback public key extraction from DOM if not caught in network
        if not captured_pk:
            captured_pk = await page.evaluate("""() => {
                const el = document.querySelector("[data-pkey]");
                return el ? el.getAttribute("data-pkey") : null;
            }""")

        print(f"Detected Arkose FunCaptcha public key: {captured_pk}")

        # Request solution from 2Captcha
        print("Dispatching challenge to 2Captcha API...")
        solve_kwargs = {
            "sitekey": captured_pk,
            "url": TARGET_URL,
            "surl": "https://client-api.arkoselabs.com"
        }
        if captured_blob:
            solve_kwargs["data"] = {"blob": captured_blob}

        result = await asyncio.to_thread(solver.funcaptcha, **solve_kwargs)
        token = result["code"]
        print(f"Received FunCaptcha token: {token[:40]}...")

        # Inject token into fc-token hidden field
        await page.evaluate("""(tokenVal) => {
            let tokenInput = document.querySelector('input[name="fc-token"], #fc-token');
            if (!tokenInput) {
                tokenInput = document.createElement("input");
                tokenInput.type = "hidden";
                tokenInput.name = "fc-token";
                tokenInput.id = "fc-token";
                document.forms[0].appendChild(tokenInput);
            }
            tokenInput.value = tokenVal;
            tokenInput.dispatchEvent(new Event("input", { bubbles: true }));
            tokenInput.dispatchEvent(new Event("change", { bubbles: true }));
        }""", token)

        print("Submitting form with solved token...")
        await page.click('button[type="submit"]')
        await page.wait_for_load_state("networkidle")
        print("Successfully bypassed Arkose FunCaptcha!")
        await browser.close()

if __name__ == "__main__":
    asyncio.run(main())

5. Common Pitfalls & Best Practices

  1. Always Capture data[blob] on Twitter & Outlook: If the target platform issues an encrypted blob, submitting without it causes instant rejection. Use Playwright request interception as demonstrated above.
  2. Match Egress IP with Proxy: If the target website tracks IP reputation closely, pass your proxy credentials into 2Captcha so the human worker or solver completes the challenge from the exact same residential IP pool.
  3. Verify Service Subdomain: Most sites use https://client-api.arkoselabs.com, but custom enterprise clients may use custom CNAME subdomains like https://arkose.target.com. Check network traffic for the origin.

6. Why 2Captcha is Rated #1 for Arkose FunCaptcha Resolution

2Captcha Arkose Benchmark Telemetry

Rank #1 Overall
  • Average Solving Speed: 14.8 seconds for multi-round 3D rotation puzzles
  • Pass Rate: 99.2% first-attempt token acceptance on Twitter, OpenAI & Outlook
  • Pricing: $1.50 - $2.99 per 1,000 solves with true Pay-As-You-Go ($0.0015/solve)
  • Blob Encryption Support: Full compatibility with ephemeral session blobs and custom proxy routing
Get 2Captcha API Key ($1.50/1k) →