Captcha solving service blog | SolveCaptcha How to bypass ALTCHA captcha with solver

How to bypass ALTCHA with solver

ALTCHA is a proof-of-work captcha that verifies whether the client completed a computational challenge. Unlike image captchas, it does not ask the user to identify objects, recognize text, or select matching pictures.

The website generates a cryptographic challenge, and the client searches for a valid solution. The completed proof is encoded into a token and submitted with the protected form or API request.

For authorized testing, data collection, and browser automation, the SolveCaptcha API can process the ALTCHA challenge and return the required token.

This guide explains how to:

  • identify ALTCHA on a page;
  • extract the challenge URL;
  • submit the challenge directly as JSON;
  • create an ALTCHA task with SolveCaptcha;
  • poll the API for the result;
  • retrieve and apply the returned token;
  • use optional proxy parameters;
  • handle expired challenges and API errors.

Use this method only on websites you own or are authorized to test and automate.

What is ALTCHA?

ALTCHA is a privacy-oriented captcha system based on cryptographic proof of work.

A typical verification flow works as follows:

  1. The website provides a challenge.
  2. The browser processes possible solution values.
  3. A valid proof is found.
  4. The proof is encoded into a payload.
  5. The payload is submitted to the website.
  6. The website verifies the proof.

ALTCHA normally runs without visual puzzles. Depending on the implementation, the user may only see a checkbox or progress indicator while the calculation is performed.

Proof of work confirms that computational work was completed. It does not independently prove that a human initiated the request.

How ALTCHA differs from traditional captcha

Feature Image captcha ALTCHA
Visual puzzle Yes No
Text recognition Sometimes No
Image selection Sometimes No
Main mechanism Human recognition Cryptographic proof of work
Input data Image or site key Challenge URL or challenge JSON
Result Text, coordinates, or token Token
Server verification Provider-specific Cryptographic validation

Because ALTCHA contains no image, it should not be submitted through image-recognition methods such as:

method=base64

The documented SolveCaptcha method is:

method=altcha

SolveCaptcha ALTCHA workflow

The integration consists of four stages:

Extract challenge
→ create SolveCaptcha task
→ retrieve result
→ submit returned token

SolveCaptcha uses the following endpoints.

Create a task:

POST https://solvecaptcha.com/in.php

Get the result:

GET https://solvecaptcha.com/res.php

The task must contain exactly one of these parameters:

challenge_url
challenge_json

Do not send both in the same request.

Required parameters

Parameter Required Description
key Yes Your SolveCaptcha API key
method Yes Must be altcha
pageurl Yes Full URL of the page containing ALTCHA
challenge_url Yes* URL used to retrieve the current challenge
challenge_json Yes* Raw contents of the challenge response
proxy No Proxy in login:password@host:port format
proxytype No HTTP, HTTPS, SOCKS4, or SOCKS5
json No Set to 1 for a JSON response

* Submit either challenge_url or challenge_json, but not both.

How to identify ALTCHA on a page

Open the target page in a browser and inspect its HTML and network requests.

Common indicators include:

<altcha-widget
  challengeurl="/api/altcha/challenge">
</altcha-widget>

The challenge may also be configured through another attribute name or JavaScript:

const configuration = {
  challengeURL:
    "/api/altcha/challenge"
};

Search the page source and loaded scripts for:

altcha-widget
challengeurl
challenge_url
ALTCHA

The challenge endpoint may use either a relative or absolute URL.

Relative value:

/api/altcha/challenge

Complete URL:

https://example.com/api/altcha/challenge

Send the complete URL to SolveCaptcha.

Extract the challenge URL with JavaScript

The following browser-console script checks common ALTCHA widget attributes:

const widget =
  document.querySelector("altcha-widget");

if (!widget) {
  throw new Error(
    "ALTCHA widget was not found"
  );
}

const value =
  widget.getAttribute("challengeurl")
  || widget.getAttribute("challenge-url")
  || widget.getAttribute("challenge_url");

if (!value) {
  throw new Error(
    "ALTCHA challenge URL was not found"
  );
}

const challengeURL =
  new URL(
    value,
    window.location.href
  ).href;

console.log(challengeURL);

Example result:

https://example.com/api/altcha/challenge

If the element does not contain the URL, open the Network tab and look for a request that returns the ALTCHA challenge JSON.

Using challenge_url

The simplest method is to send the complete challenge endpoint to SolveCaptcha:

challenge_url=https://example.com/api/altcha/challenge

SolveCaptcha will retrieve the challenge data from that URL.

This method is appropriate when:

  • the endpoint is publicly accessible;
  • it does not require browser-only headers;
  • it does not require session cookies;
  • the challenge is not restricted to the current IP;
  • the endpoint returns the challenge directly.

Using challenge_json

When the challenge endpoint depends on the active session, retrieve the challenge in your own browser or HTTP client and send its contents through:

challenge_json

A challenge response may resemble:

{
  "algorithm": "SHA-256",
  "challenge": "eab91764d3f9d0c0e8fd...",
  "maxnumber": 100000,
  "salt": "random-salt",
  "signature": "server-signature"
}

Do not modify, rename, or remove fields. Submit the challenge response exactly as returned by the website.

The JSON object must be serialized into a string before being sent as a form parameter.

Python:

import json

challenge_json = json.dumps(
    challenge_data,
    separators=(",", ":"),
)

JavaScript:

const challengeJSON =
  JSON.stringify(challengeData);

Step 1: Get your API key

Create a SolveCaptcha account and copy your API key from the account settings.

An API key looks similar to:

1abc234de56fab7c89012d34e56fa7b8

Store it in an environment variable.

Linux or macOS:

export SOLVECAPTCHA_API_KEY="YOUR_API_KEY"

Windows PowerShell:

$env:SOLVECAPTCHA_API_KEY="YOUR_API_KEY"

Do not publish the key in source code or public repositories.

Step 2: Create an ALTCHA task

Request with challenge_url

curl --request POST \
  --url https://solvecaptcha.com/in.php \
  --data-urlencode "key=YOUR_API_KEY" \
  --data-urlencode "method=altcha" \
  --data-urlencode "challenge_url=https://example.com/api/altcha/challenge" \
  --data-urlencode "pageurl=https://example.com/protected-form" \
  --data-urlencode "json=1"

A successful response contains the task ID:

{
  "status": 1,
  "request": "2037582136"
}

Save the value of request.

Request with challenge_json

curl --request POST \
  --url https://solvecaptcha.com/in.php \
  --data-urlencode "key=YOUR_API_KEY" \
  --data-urlencode "method=altcha" \
  --data-urlencode 'challenge_json={"algorithm":"SHA-256","challenge":"eab91764...","maxnumber":100000,"salt":"random-salt","signature":"server-signature"}' \
  --data-urlencode "pageurl=https://example.com/protected-form" \
  --data-urlencode "json=1"

Do not include challenge_url when submitting challenge_json.

Step 3: Retrieve the result

Send the task ID to:

https://solvecaptcha.com/res.php

Example:

curl "https://solvecaptcha.com/res.php?key=YOUR_API_KEY&action=get&id=2037582136&json=1"

If the task is still processing, wait five seconds and request the result again.

Depending on the API response format, a processing response may contain:

{
  "status": 0,
  "request": "CAPCHA_NOT_READY"
}

A completed ALTCHA task returns the token in the solution object:

{
  "cost": "0.0013",
  "createTime": 1754564127,
  "endTime": 1754564136,
  "errorId": 0,
  "ip": "51.178.94.203",
  "solution": {
    "token": "eyJhbGciOiJIUzI1NiIsImNoYWxsZW5nZSI6IjY0YjE3..."
  },
  "solveCount": 1,
  "status": "ready"
}

The required value is:

solution.token

Step 4: Apply the token

ALTCHA integrations can submit the token through:

  • a hidden form field;
  • a form payload;
  • an XHR or Fetch request;
  • an application-specific parameter;
  • a widget callback.

There is no universal field name for every website.

Use browser DevTools to determine how the protected page submits a valid ALTCHA response:

  1. Open the Network tab.
  2. Complete the protected action normally.
  3. Find the form or API request.
  4. Inspect its request payload.
  5. Identify the ALTCHA value.
  6. Submit the SolveCaptcha token through the same parameter.

A common implementation may use an input similar to:

<input
  type="hidden"
  name="altcha"
>

Example JavaScript:

const field =
  document.querySelector(
    'input[name="altcha"]'
  );

if (!field) {
  throw new Error(
    "ALTCHA response field was not found"
  );
}

field.value =
  "TOKEN_FROM_SOLVECAPTCHA";

field.dispatchEvent(
  new Event("input", {
    bubbles: true
  })
);

field.dispatchEvent(
  new Event("change", {
    bubbles: true
  })
);

The actual field name and submission method depend on the website.

Complete Python example with challenge_url

Install Requests:

python -m pip install requests

Create solve_altcha.py:

#!/usr/bin/env python3

from __future__ import annotations

import os
import time
from dataclasses import dataclass
from typing import Any

import requests

CREATE_TASK_URL = (
    "https://solvecaptcha.com/in.php"
)

GET_RESULT_URL = (
    "https://solvecaptcha.com/res.php"
)

POLL_INTERVAL_SECONDS = 5
MAX_WAIT_SECONDS = 120

class SolveCaptchaError(RuntimeError):
    """Raised when SolveCaptcha returns an error."""

@dataclass(frozen=True)
class AltchaSolution:
    task_id: str
    token: str

def parse_response(
    response: requests.Response,
) -> dict[str, Any]:
    response.raise_for_status()

    try:
        result = response.json()
    except ValueError as exc:
        raise SolveCaptchaError(
            "SolveCaptcha returned invalid JSON: "
            f"{response.text[:500]}"
        ) from exc

    if not isinstance(result, dict):
        raise SolveCaptchaError(
            f"Unexpected API response: {result!r}"
        )

    return result

class AltchaClient:
    def __init__(
        self,
        api_key: str,
        request_timeout: float = 30,
    ) -> None:
        if not api_key:
            raise ValueError(
                "SolveCaptcha API key is required"
            )

        self.api_key = api_key
        self.request_timeout = request_timeout
        self.session = requests.Session()

    def create_task(
        self,
        *,
        pageurl: str,
        challenge_url: str | None = None,
        challenge_json: str | None = None,
        proxy: str | None = None,
        proxytype: str | None = None,
    ) -> str:
        if bool(challenge_url) == bool(challenge_json):
            raise ValueError(
                "Provide exactly one of "
                "challenge_url or challenge_json"
            )

        payload: dict[str, Any] = {
            "key": self.api_key,
            "method": "altcha",
            "pageurl": pageurl,
            "json": 1,
        }

        if challenge_url:
            payload["challenge_url"] = (
                challenge_url
            )

        if challenge_json:
            payload["challenge_json"] = (
                challenge_json
            )

        if proxy:
            payload["proxy"] = proxy

        if proxytype:
            payload["proxytype"] = (
                proxytype
            )

        response = self.session.post(
            CREATE_TASK_URL,
            data=payload,
            timeout=self.request_timeout,
        )

        result = parse_response(response)

        if result.get("status") != 1:
            raise SolveCaptchaError(
                "Task creation failed: "
                f"{result.get('request')}"
            )

        task_id = str(
            result.get("request", "")
        ).strip()

        if not task_id:
            raise SolveCaptchaError(
                "SolveCaptcha did not return "
                "a task ID"
            )

        return task_id

    def get_result(
        self,
        task_id: str,
    ) -> str | None:
        response = self.session.get(
            GET_RESULT_URL,
            params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1,
            },
            timeout=self.request_timeout,
        )

        result = parse_response(response)

        if result.get("status") == "ready":
            solution = result.get("solution")

            if not isinstance(
                solution,
                dict,
            ):
                raise SolveCaptchaError(
                    "The response does not "
                    "contain a solution object"
                )

            token = str(
                solution.get("token", "")
            ).strip()

            if not token:
                raise SolveCaptchaError(
                    "SolveCaptcha returned "
                    "an empty ALTCHA token"
                )

            return token

        if result.get("status") == 1:
            token = str(
                result.get("request", "")
            ).strip()

            if token:
                return token

        error_code = str(
            result.get(
                "request",
                result.get(
                    "errorCode",
                    "CAPCHA_NOT_READY",
                ),
            )
        )

        if (
            error_code == "CAPCHA_NOT_READY"
            or result.get("status")
            == "processing"
        ):
            return None

        if result.get("errorId", 0) != 0:
            raise SolveCaptchaError(
                "ALTCHA task failed: "
                f"{error_code}"
            )

        return None

    def solve(
        self,
        *,
        pageurl: str,
        challenge_url: str | None = None,
        challenge_json: str | None = None,
        proxy: str | None = None,
        proxytype: str | None = None,
    ) -> AltchaSolution:
        task_id = self.create_task(
            pageurl=pageurl,
            challenge_url=challenge_url,
            challenge_json=challenge_json,
            proxy=proxy,
            proxytype=proxytype,
        )

        deadline = (
            time.monotonic()
            + MAX_WAIT_SECONDS
        )

        while time.monotonic() < deadline:
            time.sleep(
                POLL_INTERVAL_SECONDS
            )

            token = self.get_result(
                task_id
            )

            if token is not None:
                return AltchaSolution(
                    task_id=task_id,
                    token=token,
                )

        raise TimeoutError(
            f"ALTCHA task {task_id} "
            f"was not completed within "
            f"{MAX_WAIT_SECONDS} seconds"
        )

def main() -> None:
    api_key = os.environ.get(
        "SOLVECAPTCHA_API_KEY",
        "",
    ).strip()

    if not api_key:
        raise SystemExit(
            "Set SOLVECAPTCHA_API_KEY"
        )

    client = AltchaClient(
        api_key=api_key
    )

    result = client.solve(
        pageurl=(
            "https://example.com/"
            "protected-form"
        ),
        challenge_url=(
            "https://example.com/"
            "api/altcha/challenge"
        ),
    )

    print(
        "Task ID:",
        result.task_id,
    )

    print(
        "Token:",
        result.token,
    )

if __name__ == "__main__":
    main()

Run the script:

python solve_altcha.py

Python example with challenge_json

Use challenge_json when you need to retrieve the challenge through the active session:

import json
import os

import requests

api_key = os.environ[
    "SOLVECAPTCHA_API_KEY"
]

page_url = (
    "https://example.com/"
    "protected-form"
)

challenge_url = (
    "https://example.com/"
    "api/altcha/challenge"
)

session = requests.Session()

challenge_response = session.get(
    challenge_url,
    headers={
        "Referer": page_url,
    },
    timeout=30,
)

challenge_response.raise_for_status()
challenge_data = (
    challenge_response.json()
)

result = client.solve(
    pageurl=page_url,
    challenge_json=json.dumps(
        challenge_data,
        separators=(",", ":"),
    ),
)

print(result.token)

This method allows your client to preserve any required cookies, headers, or authentication when requesting the challenge.

Node.js example

Node.js 18 and later includes the Fetch API.

Create solve-altcha.js:

const CREATE_TASK_URL =
  "https://solvecaptcha.com/in.php";

const GET_RESULT_URL =
  "https://solvecaptcha.com/res.php";

const API_KEY =
  process.env.SOLVECAPTCHA_API_KEY;

if (!API_KEY) {
  throw new Error(
    "Set SOLVECAPTCHA_API_KEY"
  );
}

const sleep = (milliseconds) =>
  new Promise((resolve) =>
    setTimeout(resolve, milliseconds)
  );

async function readJson(response) {
  const text = await response.text();

  if (!response.ok) {
    throw new Error(
      `HTTP ${response.status}: ${text}`
    );
  }

  try {
    return JSON.parse(text);
  } catch {
    throw new Error(
      `Invalid JSON response: ${text}`
    );
  }
}

async function createAltchaTask({
  pageURL,
  challengeURL = null,
  challengeJSON = null,
  proxy = null,
  proxyType = null,
}) {
  if (
    Boolean(challengeURL)
    === Boolean(challengeJSON)
  ) {
    throw new Error(
      "Provide exactly one of "
      + "challengeURL or challengeJSON"
    );
  }

  const body = new URLSearchParams({
    key: API_KEY,
    method: "altcha",
    pageurl: pageURL,
    json: "1",
  });

  if (challengeURL) {
    body.set(
      "challenge_url",
      challengeURL
    );
  }

  if (challengeJSON) {
    body.set(
      "challenge_json",
      challengeJSON
    );
  }

  if (proxy) {
    body.set("proxy", proxy);
  }

  if (proxyType) {
    body.set(
      "proxytype",
      proxyType
    );
  }

  const response = await fetch(
    CREATE_TASK_URL,
    {
      method: "POST",
      headers: {
        "Content-Type":
          "application/x-www-form-urlencoded",
      },
      body,
    }
  );

  const result =
    await readJson(response);

  if (result.status !== 1) {
    throw new Error(
      `Task creation failed: `
      + `${result.request}`
    );
  }

  return String(result.request);
}

async function getAltchaResult(
  taskId
) {
  const url = new URL(
    GET_RESULT_URL
  );

  url.search = new URLSearchParams({
    key: API_KEY,
    action: "get",
    id: taskId,
    json: "1",
  }).toString();

  const response = await fetch(url);
  const result =
    await readJson(response);

  if (result.status === "ready") {
    const token =
      result.solution?.token;

    if (!token) {
      throw new Error(
        "ALTCHA token is missing"
      );
    }

    return token;
  }

  if (
    result.status === 1
    && result.request
  ) {
    return String(result.request);
  }

  if (
    result.status === "processing"
    || result.request
      === "CAPCHA_NOT_READY"
  ) {
    return null;
  }

  if (result.errorId) {
    throw new Error(
      `Task failed: `
      + `${result.errorCode
        || result.request
        || "UNKNOWN_ERROR"}`
    );
  }

  return null;
}

async function solveAltcha({
  pageURL,
  challengeURL = null,
  challengeJSON = null,
}) {
  const taskId =
    await createAltchaTask({
      pageURL,
      challengeURL,
      challengeJSON,
    });

  const deadline =
    Date.now() + 120_000;

  while (Date.now() < deadline) {
    await sleep(5_000);

    const token =
      await getAltchaResult(
        taskId
      );

    if (token) {
      return {
        taskId,
        token,
      };
    }
  }

  throw new Error(
    `ALTCHA task ${taskId} timed out`
  );
}

const result = await solveAltcha({
  pageURL:
    "https://example.com/"
    + "protected-form",
  challengeURL:
    "https://example.com/"
    + "api/altcha/challenge",
});

console.log(
  "Task ID:",
  result.taskId
);

console.log(
  "Token:",
  result.token
);

Run it:

SOLVECAPTCHA_API_KEY=YOUR_API_KEY \
node solve-altcha.js

Using a proxy

Proxy parameters are optional.

To send a proxy, use:

proxy=login:password@host:port
proxytype=HTTP

Supported proxy types are:

HTTP
HTTPS
SOCKS4
SOCKS5

Python example:

result = client.solve(
    pageurl=(
        "https://example.com/"
        "protected-form"
    ),
    challenge_url=(
        "https://example.com/"
        "api/altcha/challenge"
    ),
    proxy=(
        "login:password@"
        "proxy.example.com:8000"
    ),
    proxytype="HTTP",
)

Use a proxy when the challenge endpoint or target workflow requires the solving request to originate through a particular network path.

Do not rotate the proxy while one challenge is being processed. A new IP may cause the website to issue a new challenge.

Challenge freshness

ALTCHA challenges may be limited by:

  • expiration time;
  • server-side configuration;
  • one-time use;
  • page state;
  • application session;
  • challenge signature.

Retrieve a fresh challenge immediately before creating the task.

Do not:

  • store challenges for later use;
  • reuse a completed challenge;
  • submit a challenge from another hostname;
  • modify signed challenge fields;
  • apply the token to a different page;
  • reuse one token for multiple requests.

When the page generates a new challenge, create a new SolveCaptcha task.

Common errors

ERROR_WRONG_USER_KEY

The API key is invalid.

Copy the key again from your SolveCaptcha account.

ERROR_ZERO_BALANCE

The account does not have sufficient balance.

Add funds before creating another task.

ERROR_CAPTCHA_UNSOLVABLE

The challenge could not be processed.

Check:

  • whether the challenge is fresh;
  • whether pageurl is correct;
  • whether challenge_url is accessible;
  • whether challenge_json is complete;
  • whether both challenge parameters were accidentally submitted;
  • whether the proxy is working.

Retrieve a fresh challenge before retrying.

CAPCHA_NOT_READY

The task is still processing.

Wait five seconds and request the result again using the same task ID.

Challenge endpoint returns 401 or 403

The challenge URL may require:

  • session cookies;
  • authentication;
  • a Referer header;
  • a particular source IP;
  • another request header.

Retrieve the challenge through your active session and submit its contents through:

challenge_json

Token rejected by the website

Possible causes include:

  • expired challenge;
  • incorrect page URL;
  • token submitted to the wrong field;
  • changed application session;
  • challenge already used;
  • token applied to another request;
  • proxy or IP change;
  • missing application data.

Inspect the normal form or API request to determine exactly where the website expects the token.

Common implementation mistakes

Using API v2 task names

Do not send:

AltchaTask
AltchaTaskProxyless
clientKey
websiteURL
challengeURL
challengeJSON

The documented SolveCaptcha request uses:

key
method=altcha
pageurl
challenge_url
challenge_json

Using createTask and getTaskResult

The documented integration uses:

https://solvecaptcha.com/in.php

and:

https://solvecaptcha.com/res.php

Sending both challenge parameters

Incorrect:

challenge_url=...
challenge_json=...

Correct:

challenge_url=...

or:

challenge_json=...

Modifying challenge JSON

Send the challenge contents exactly as returned by the website.

Reusing a challenge

Create a new task whenever the challenge changes.

Polling too frequently

Use an interval of approximately five seconds.

Guessing the token field

Inspect the actual browser request instead of assuming that every website uses the same field name.

Frequently asked questions

Which SolveCaptcha method is used for ALTCHA?

Use:

method=altcha

Which parameters are required?

Every request requires:

key
method
pageurl

It must also contain exactly one of:

challenge_url
challenge_json

Is a proxy required?

No. The SolveCaptcha documentation defines proxy and proxytype as optional.

What is challenge_url?

It is the complete URL used by the ALTCHA widget to retrieve the cryptographic challenge.

What is challenge_json?

It is the unmodified JSON response retrieved from the challenge endpoint.

Can I submit both?

No. Submit either challenge_url or challenge_json.

Where is the task ID returned?

A successful task-creation response returns it in:

request

Where is the token returned?

A completed result contains:

solution.token

How often should I request the result?

Poll approximately every five seconds until the task is ready or the configured timeout is reached.

Where should the token be inserted?

Inspect the protected form or API request. The token may be sent through a hidden field, request body, or application-specific callback.

Can a token be reused?

No. Treat each token as challenge-specific and single-use.

Conclusion

Solving ALTCHA with SolveCaptcha requires the original cryptographic challenge rather than an image or site key.

The correct workflow is:

  1. Find the ALTCHA widget or challenge network request.
  2. Extract the complete challenge endpoint.
  3. Choose either challenge_url or challenge_json.
  4. Submit method=altcha to in.php.
  5. Include the full protected page URL as pageurl.
  6. Save the returned task ID.
  7. Poll res.php approximately every five seconds.
  8. Read the completed token from solution.token.
  9. Submit the token through the field or request expected by the website.
  10. Retrieve a fresh challenge whenever the page creates a new one.

Use the SolveCaptcha ALTCHA documentation for the current parameter list and contact support when a website uses a customized ALTCHA integration.