GeeTest v3 is a slide-puzzle CAPTCHA common on Chinese websites and global platforms. Solving it requires extracting two parameters — gt and challenge — and submitting three response values. This guide covers the complete Node.js flow.
GeeTest v3 parameters
| Parameter | Description | Where to find |
|---|---|---|
gt |
Public key (static per site) | HTML source or API response |
challenge |
One-time challenge token | API response (changes per load) |
The solve returns three values:
| Response field | Purpose |
|---|---|
geetest_challenge |
Verified challenge string |
geetest_validate |
Validation hash |
geetest_seccode |
Security code (validate + |jordan) |
Step 1: Extract gt and challenge
Method A: From HTML source
async function extractGeeTestFromHTML(url) {
const resp = await fetch(url, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36",
},
});
const html = await resp.text();
const gtMatch = html.match(/gt\s*[:=]\s*["']([a-f0-9]{32})["']/);
const challengeMatch = html.match(
/challenge\s*[:=]\s*["']([a-f0-9]{32})["']/
);
if (!gtMatch) throw new Error("GeeTest gt not found in HTML");
if (!challengeMatch) throw new Error("GeeTest challenge not found in HTML");
return {
gt: gtMatch[1],
challenge: challengeMatch[1],
};
}
Method B: From API endpoint
Many sites load GeeTest parameters from an API:
async function extractGeeTestFromAPI(apiUrl) {
const resp = await fetch(apiUrl, {
headers: {
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36",
},
});
const data = await resp.json();
// Common response formats
const gt = data.gt || data.data?.gt || data.captcha?.gt;
const challenge =
data.challenge || data.data?.challenge || data.captcha?.challenge;
if (!gt || !challenge) {
throw new Error("GeeTest parameters not found in API response");
}
return { gt, challenge };
}
Method C: Intercept network requests
// When using Puppeteer/Playwright, intercept the GeeTest API call
async function interceptGeeTest(page) {
return new Promise((resolve) => {
page.on("response", async (response) => {
const url = response.url();
if (
url.includes("api.geetest.com/register") ||
url.includes("geetest") ||
url.includes("gt=")
) {
try {
const data = await response.json();
if (data.gt && data.challenge) {
resolve({ gt: data.gt, challenge: data.challenge });
}
} catch {}
}
});
});
}
Step 2: Solve via CaptchaAI
const API_KEY = "YOUR_API_KEY";
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function solveGeeTestV3(gt, challenge, pageurl) {
// Submit
const submitResp = await fetch("https://ocr.captchaai.com/in.php", {
method: "POST",
body: new URLSearchParams({
key: API_KEY,
method: "geetest",
gt: gt,
challenge: challenge,
pageurl: pageurl,
json: "1",
}),
});
const submitData = await submitResp.json();
if (submitData.status !== 1) {
throw new Error(`Submit error: ${submitData.request}`);
}
const taskId = submitData.request;
console.log(`Task ID: ${taskId}`);
// Poll
for (let i = 0; i < 30; i++) {
await sleep(5000);
const pollResp = await fetch(
`https://ocr.captchaai.com/res.php?${new URLSearchParams({
key: API_KEY,
action: "get",
id: taskId,
json: "1",
})}`
);
const pollData = await pollResp.json();
if (pollData.status === 1) {
// Parse the GeeTest response
const response =
typeof pollData.request === "string"
? JSON.parse(pollData.request)
: pollData.request;
return {
geetest_challenge: response.geetest_challenge,
geetest_validate: response.geetest_validate,
geetest_seccode: response.geetest_seccode,
};
}
if (pollData.request === "ERROR_CAPTCHA_UNSOLVABLE") {
throw new Error("GeeTest unsolvable");
}
}
throw new Error("Solve timed out");
}
Step 3: Submit the response
async function submitGeeTestForm(url, formData, geetestResponse) {
const body = new URLSearchParams({
...formData,
geetest_challenge: geetestResponse.geetest_challenge,
geetest_validate: geetestResponse.geetest_validate,
geetest_seccode: geetestResponse.geetest_seccode,
});
const resp = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36",
},
body,
});
return {
status: resp.status,
body: await resp.json().catch(() => resp.text()),
};
}
Complete flow example
async function loginWithGeeTest(loginUrl, apiUrl, credentials) {
// Step 1: Get GeeTest parameters
const { gt, challenge } = await extractGeeTestFromAPI(apiUrl);
console.log(`GT: ${gt}`);
console.log(`Challenge: ${challenge}`);
// Step 2: Solve
const response = await solveGeeTestV3(gt, challenge, loginUrl);
console.log("GeeTest solved:", Object.keys(response));
// Step 3: Submit
const result = await submitGeeTestForm(loginUrl, credentials, response);
console.log(`Result: ${result.status}`);
return result;
}
// Usage
const result = await loginWithGeeTest(
"https://example.com/login",
"https://example.com/api/captcha/init",
{ username: "user", password: "pass123" }
);
Production solver class
class GeeTestSolver {
#apiKey;
constructor(apiKey) {
this.#apiKey = apiKey;
}
async solve(gt, challenge, pageurl) {
const taskId = await this.#submit(gt, challenge, pageurl);
return await this.#poll(taskId);
}
async #submit(gt, challenge, pageurl) {
const resp = await fetch("https://ocr.captchaai.com/in.php", {
method: "POST",
body: new URLSearchParams({
key: this.#apiKey,
method: "geetest",
gt,
challenge,
pageurl,
json: "1",
}),
});
const data = await resp.json();
if (data.status !== 1) throw new Error(`Submit: ${data.request}`);
return data.request;
}
async #poll(taskId) {
const params = new URLSearchParams({
key: this.#apiKey,
action: "get",
id: taskId,
json: "1",
});
for (let i = 0; i < 30; i++) {
await new Promise((r) => setTimeout(r, 5000));
const resp = await fetch(`https://ocr.captchaai.com/res.php?${params}`);
const data = await resp.json();
if (data.status === 1) {
const result =
typeof data.request === "string"
? JSON.parse(data.request)
: data.request;
return {
geetest_challenge: result.geetest_challenge,
geetest_validate: result.geetest_validate,
geetest_seccode: result.geetest_seccode,
};
}
if (data.request === "ERROR_CAPTCHA_UNSOLVABLE") {
throw new Error("Unsolvable");
}
}
throw new Error("Timed out");
}
}
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
challenge is empty |
Challenge not loaded yet | Fetch challenge from API first |
| Response fields are undefined | JSON parsing issue | Check typeof data.request |
| Server rejects response | Stale challenge token | Get fresh challenge before solving |
ERROR_BAD_PARAMETERS |
Missing gt or challenge | Verify both are 32-char hex strings |
| Different challenge on each load | Normal — challenge is one-time | Always get fresh challenge |
Frequently asked questions
What's the difference between GeeTest v3 and v4?
v3 uses gt + challenge parameters. v4 uses captcha_id and has a different response format. Use method=geetest for v3 and method=geetest_v4 for v4.
Why does the challenge change every time?
The challenge is a one-time token generated by GeeTest's server. You must extract a fresh challenge for each solve attempt.
What does geetest_seccode contain?
It's typically {geetest_validate}|jordan — a concatenation of the validate value and a fixed suffix.
Summary
Solve GeeTest v3 with Node.js and CaptchaAI: extract gt and challenge from the page or API, solve with method=geetest, and submit the three response fields (geetest_challenge, geetest_validate, geetest_seccode).
Discussions (0)
Join the conversation
Sign in to share your opinion.
Sign InNo comments yet.