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:
- The website provides a challenge.
- The browser processes possible solution values.
- A valid proof is found.
- The proof is encoded into a payload.
- The payload is submitted to the website.
- 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:
- Open the Network tab.
- Complete the protected action normally.
- Find the form or API request.
- Inspect its request payload.
- Identify the ALTCHA value.
- 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
pageurlis correct; - whether
challenge_urlis accessible; - whether
challenge_jsonis 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:
- Find the ALTCHA widget or challenge network request.
- Extract the complete challenge endpoint.
- Choose either
challenge_urlorchallenge_json. - Submit
method=altchatoin.php. - Include the full protected page URL as
pageurl. - Save the returned task ID.
- Poll
res.phpapproximately every five seconds. - Read the completed token from
solution.token. - Submit the token through the field or request expected by the website.
- 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.