SurSolver

BlogProsopo Procaptcha solver API: step-by-step guide

Prosopo Procaptcha solver API: step-by-step guide

· Updated · 8 min read

This guide takes you from a page that shows a Procaptcha widget to a submitted form. You will find the sitekey, create a Prosopo task, poll for the token and place it in the procaptcha-response field. The code is Python with requests and Playwright, and every call matches the SurSolver API.

What Procaptcha is

Procaptcha is the widget from Prosopo, positioned as a privacy-focused, open-source alternative to reCAPTCHA. It works in tiers: a frictionless background check, a proof-of-work step where the browser solves a small computation, and an image-selection challenge shown only when the visitor looks suspicious.

For you, the tiers collapse into one token. A Prosopo task takes a page URL and a sitekey and returns a single token string, whichever tier the visitor would have faced. You never choose a tier, and no separate answer format exists for the proof-of-work or image steps.

A page uses Procaptcha when it has a container such as <div class="procaptcha" data-sitekey="..."> and loads js.prosopo.io/js/procaptcha.bundle.js. The site then verifies the submitted token against Prosopo's siteverify endpoint with its own secret key. You never see that secret; your only job is to deliver a token the site accepts. The Prosopo solver guide covers recognising the widget in more detail.

Find the sitekey and page URL

You need two values, websiteURL and websiteKey.

For the sitekey, open the protected page, press F12 and search the Elements panel for data-sitekey. The value on the procaptcha container is the key. If the page renders the widget from JavaScript, the same key is the siteKey option of the procaptcha.render(...) call, so check the page scripts as well as the HTML.

If neither shows it, reload with the Network tab open and look for requests to prosopo.io or procaptcha.io that carry the key, or a Prosopo-Site-Key request header.

For websiteURL, use the full URL of the page that loads the widget, including the query string when the widget is tied to a path. It has to belong to the protected domain, or verification fails.

Each widget has its own sitekey and secret pair. A key copied from another site will not produce a token that this site accepts, so always read it from the page you submit to. The post on identifying a captcha and extracting its parameters shows the general technique.

Choose ProsopoTaskProxyless or ProsopoTask

SurSolver accepts two names for Prosopo:

task.type Proxy Use it when
ProsopoTaskProxyless none Default. The solve runs from SurSolver infrastructure.
ProsopoTask required The site checks that the IP that solved the widget is the IP that submits the token.

ProsopoToken is also accepted as a name, but it does not make the proxy choice explicit, so prefer the two names above. Sending ProsopoTask without proxy details is rejected with ERROR_INVALID_REQUEST.

Start with ProsopoTaskProxyless. Switch to ProsopoTask only when a valid-looking token is rejected while the same form works by hand. Then send your own proxy in task.proxy and submit the token through that same proxy:

snippet.json
{
  "clientKey": "YOUR_API_KEY",
  "task": {
    "type": "ProsopoTask",
    "websiteURL": "https://www.example.com/signup",
    "websiteKey": "YOUR_PROCAPTCHA_SITEKEY",
    "proxy": "http://user:password@203.0.113.10:8000"
  }
}

A proxy string with four colon-separated parts is always read as user:password:host:port. The proxies guide lists every accepted format, and proxies for captcha solving explains how to choose one.

Create the task

The proxyless request needs only the page URL and the key. userAgent is optional:

solve.sh
curl -sS -X POST "https://api.sursolver.com/createTask" \
  -H "Content-Type: application/json" \
  -d '{
    "clientKey": "YOUR_API_KEY",
    "task": {
      "type": "ProsopoTaskProxyless",
      "websiteURL": "https://www.example.com/signup",
      "websiteKey": "YOUR_PROCAPTCHA_SITEKEY"
    }
  }'

An accepted task answers with an id:

snippet.json
{"errorId":0,"taskId":"tsk_9c1f4e2a7b0d5e3f81a62c47","status":"idle"}

The task is charged at this point. API errors come back as HTTP 200 with errorId: 1, so branch on errorId and not on the status code.

Poll for the result

Call getTaskResult with the taskId every 3 seconds. While the task is queued or being solved, the answer is:

snippet.json
{"errorId":0,"status":"processing"}

When it is done, the solution holds the token:

snippet.json
{
  "errorId": 0,
  "status": "ready",
  "solution": {
    "token": "0x00016c68747470733a2f2f...",
    "userAgent": "Mozilla/5.0 ..."
  }
}

Treat token as an opaque string. userAgent is conditional: it is present only when the solver reported one, so read it with a default. When it is there, use that user agent for the request that submits the token.

On errorId: 1, stop polling. A failed task is final, and trying again means submitting a new task. The API sets no polling ceiling, so give your own loop a deadline.

Put the token into procaptcha-response

On a standard integration the token goes into the form field named procaptcha-response, and the site verifies it with Prosopo after the form is submitted. Three shapes are common:

  • A plain form POST: send procaptcha-response=<token> together with the other fields.
  • A script-driven widget: call the callback the page passed to the widget, with the token.
  • A custom integration: complete the captcha once by hand, read the request in the Network tab and copy the field name and location the site actually uses.

Tokens are short-lived, so submit right after the task becomes ready. How to submit a captcha token goes through each pattern.

Python and Playwright example

The script below creates the task, polls it with a deadline, then opens a browser with the returned user agent and fills the field. It solves before opening the browser because a Playwright context takes its user agent when it is created.

solve.py
import os
import time

import requests
from playwright.sync_api import sync_playwright

API = 'https://api.sursolver.com'
API_KEY = os.environ['SURSOLVER_API_KEY']
PAGE_URL = 'https://www.example.com/signup'
SITEKEY = 'YOUR_PROCAPTCHA_SITEKEY'
POLL_SECONDS = 3
DEADLINE_SECONDS = 180  # client-side limit, the API does not set one

SET_TOKEN = """(token) => {
  const form = document.querySelector('form');
  let field = form.querySelector('[name="procaptcha-response"]');
  if (!field) {
    field = document.createElement('input');
    field.type = 'hidden';
    field.name = 'procaptcha-response';
    form.appendChild(field);
  }
  field.value = token;
}"""


def call(path, body):
    res = requests.post(f'{API}{path}', json={'clientKey': API_KEY, **body}, timeout=30)
    res.raise_for_status()  # HTTP 500 has no errorId: a server fault
    data = res.json()
    if data.get('errorId') != 0:
        raise RuntimeError(f"{data.get('errorCode')}: {data.get('errorDescription')}")
    return data


def solve_procaptcha(page_url, sitekey):
    created = call('/createTask', {'task': {
        'type': 'ProsopoTaskProxyless',
        'websiteURL': page_url,
        'websiteKey': sitekey,
    }})
    task_id = created['taskId']
    deadline = time.monotonic() + DEADLINE_SECONDS
    while time.monotonic() < deadline:
        time.sleep(POLL_SECONDS)
        result = call('/getTaskResult', {'taskId': task_id})
        if result['status'] == 'ready':
            return result['solution']
    raise TimeoutError(f'task {task_id} still processing after {DEADLINE_SECONDS}s')


solution = solve_procaptcha(PAGE_URL, SITEKEY)

with sync_playwright() as p:
    browser = p.chromium.launch()
    context = browser.new_context(user_agent=solution.get('userAgent'))
    page = context.new_page()
    page.goto(PAGE_URL)
    page.evaluate(SET_TOKEN, solution['token'])
    page.click('form [type="submit"]')
    page.wait_for_load_state('networkidle')
    print(page.url)
    browser.close()

Passing user_agent=None leaves Playwright's default in place, which is what you want when the solution carries no user agent. If your target takes the token through a callback or an XHR instead of a form field, replace SET_TOKEN with the code that does that. The Playwright recipe covers those variants.

For the proxy variant, change the task type to ProsopoTask, add proxy, and launch Chromium with the same proxy so the submission leaves from the IP that solved.

Common failures

The token verifies as false. There are three usual causes. The token sat unused for too long. The sitekey or domain does not match the page. Or the site ties each token to a session id rendered in its own widget; Prosopo's session correlation does this, and a token produced outside that session cannot pass. On a site hardened like that, third-party tokens can fail even when everything else is right, and no task parameter works around it.

The token is rejected only from your server. Check whether the site validates the solving IP. If it does, move to ProsopoTask with your own proxy and submit through it.

ERROR_INVALID_REQUEST. A required field is missing or malformed. errorDescription names it, and nothing was charged.

ERROR_FAILED. The solve failed. The task was charged and the failure is final. SurSolver returns no Prosopo-specific error codes, so read errorDescription and check the sitekey and URL before you resubmit.

The user agent and headers post explains why the submission should look like the browser that earned the token.

What it costs

A task is charged when createTask accepts it. A successful task keeps that charge, and a task that finishes as failed automatically returns its original charge once. A retry is a new task and a new charge, so fix the cause before you loop. The price of each service is on the pricing page, Billing & packages has the details, and for high volumes you can contact support about custom rates.

Next steps

Create an API key in the dashboard and send one ProsopoTaskProxyless task for your page. Keep the public catalog (GET /v1/task-types) open for the full parameter list, and read Task lifecycle before you put the loop into production. If your target ties tokens to the solving IP, the CaptchaFox guide shows the same flow with a mandatory proxy.

More articles