How to Fix ERROR_GOOGLEKEY and ERROR_PAGEURL on Submit

Short answer: ERROR_GOOGLEKEY means the googlekey value you sent was rejected, and ERROR_PAGEURL means the pageurl value was. Both come back from /in.php at submit time, so no task was created and there is no captcha ID to poll. Nine times out of ten the cause is a parameter name: reCAPTCHA wants googlekey, Turnstile wants sitekey, and sending the wrong one produces exactly this. Here is how to tell them apart and fix each.
What the two codes actually check
| Code | Documented meaning | Which parameter |
|---|---|---|
ERROR_GOOGLEKEY | Invalid googlekey parameter | The sitekey you read off the page |
ERROR_PAGEURL | Invalid pageurl parameter | The URL of the page the widget sits on |
They are validation failures, not solve failures. Nothing has been attempted yet, and nothing is queued.
Submit-time, not poll-time
This distinction saves a lot of debugging. The API has two stages, and each one produces its own family of errors.
# Stage 1: submit. These codes appear HERE. curl -X POST http://127.0.0.1:8080/in.php \ -d "key=YOUR_API_KEY" -d "method=userrecaptcha" \ -d "sitekey=YOUR_SITEKEY" \ -d "pageurl=https://example.com/page-with-recaptcha" ERROR_GOOGLEKEY # no ID came back, so there is nothing to poll
Compare that with a healthy submit, which returns OK|2122988149 and then fails, if it fails at all, on /res.php. A poll-time failure means your parameters were accepted and the solve itself did not work, which is a completely different investigation. See ERROR_CAPTCHA_UNSOLVABLE for that one, and ERROR_KEY_DOES_NOT_EXIST if the key itself is being rejected before either check runs.
Why the value gets rejected
Three causes account for nearly all of these, in this order.
Cause one: googlekey against sitekey
The 2captcha-compatible API uses a different parameter name per method, and they are not interchangeable.
| Method | Key parameter | Wrong name gives you |
|---|---|---|
userrecaptcha | googlekey | ERROR_GOOGLEKEY |
turnstile | sitekey | ERROR_BAD_PARAMETERS |
geetest | gt plus challenge | ERROR_BAD_PARAMETERS |
The trap is that everybody calls the value a sitekey in conversation, and the HTML attribute is literally data-sitekey for both reCAPTCHA and Turnstile. Only the reCAPTCHA submit parameter is named after Google. If you copied a working Turnstile call and changed method to userrecaptcha, this is your bug.
The SDKs hide the difference entirely. solver.recaptcha(sitekey, url) and solver.turnstile(sitekey, url) take the same first argument and map it to the right wire name for you, which is a good reason to use one.
Cause two: the key is stale, cropped or empty
Assuming the parameter name is right, the value itself is the next suspect.
- You hardcoded it. Sites rotate sitekeys. A constant that worked last month can be dead today.
- It came from cached page source. View-source in a browser can serve you an older copy than the one the widget actually rendered from.
- You grabbed the wrong attribute. The value lives in
data-sitekey, not inid,nameor the iframe’stitle. - A shell variable brought a newline with it. Command substitution keeps trailing whitespace, and whitespace is not part of a valid key.
- It is empty. An unset variable expands to nothing, and the request goes out with
googlekey=.
Read it from the live DOM instead of trusting a constant:
// Run in the page, or via your automation tool's evaluate().
// reCAPTCHA and Turnstile both expose it as data-sitekey.
const recaptchaKey = document.querySelector('.g-recaptcha')?.dataset.sitekey;
const turnstileKey = document.querySelector('.cf-turnstile')?.dataset.sitekey;
// Fallback: reCAPTCHA also carries it as k= in the widget iframe URL.
const iframe = document.querySelector('iframe[src*="/recaptcha/"]');
const fromSrc = iframe ? new URL(iframe.src).searchParams.get('k') : null;
console.log(recaptchaKey || fromSrc);The k= fallback matters on sites that render the widget through JavaScript and never leave a .g-recaptcha element in the DOM.
Cause three: what makes a pageurl invalid
ERROR_PAGEURL is a narrower problem, and usually mechanical.
- No scheme.
example.com/loginis not a URL. Sendhttps://example.com/login. - A relative path.
/loginis what your scraper had in hand, not what the API needs. - GET truncation. If the page URL contains its own query string and you submit over GET without encoding it, everything after the first ampersand is parsed as a parameter of your API call. POST, or use
--data-urlencode. - Whitespace or quotes. A URL read out of a config file can arrive wrapped in quotes that were never stripped.
The URL does not have to be reachable from the machine running CapSkip, and it does not have to be the URL you eventually POST the form to. It has to be the page the widget is embedded in, because that is what the token gets bound to.
How this looks from an SDK
The SDKs check obvious problems before anything leaves your process, and pass the rest through. Two different exceptions, two different fixes.
# pip install capskip
from capskip import CapSkip, ApiException, ValidationException
solver = CapSkip(host="127.0.0.1", port=8080)
try:
result = solver.recaptcha(
sitekey=sitekey_from_dom,
url="https://example.com/page-with-recaptcha",
)
except ValidationException:
# Caught locally: empty sitekey, malformed URL, missing argument.
print("fix the values before sending")
except ApiException as e:
# Came back from the API: GOOGLEKEY or PAGEURL rejected.
print(f"rejected at submit: {e}")A ValidationException means you never reached the API. An ApiException carrying one of these codes means you did, and the value was refused. Same idea in C#, with the base type doing the work:
using CapSkip;
var solver = new CapSkipClient(host: "127.0.0.1", port: 8080);
try
{
var result = await solver.RecaptchaAsync(sitekey, pageUrl);
}
catch (ApiException ex)
{
// ERROR_GOOGLEKEY or ERROR_PAGEURL arrives here.
Console.WriteLine(ex.Message);
}
catch (CapSkipError ex)
{
// Everything else the SDK can throw.
Console.WriteLine(ex.Message);
}A two-minute checklist
- Print the exact request body you are sending. Not the variables, the body.
- Check the parameter name against the method:
googlekeyfor reCAPTCHA,sitekeyfor Turnstile. - Confirm the value is non-empty and has no trailing newline.
- Re-read the sitekey from the live page and compare it to the one you sent.
- Confirm
pageurlstarts withhttp://orhttps://. - Switch the call to POST if the page URL has a query string.
Step one solves this more often than the other five combined, because the mismatch is usually between what you think you are sending and what is on the wire.
Nearby codes and what they mean instead
| Code | Means |
|---|---|
ERROR_WRONG_USER_KEY | The API key is missing or empty, checked before either of these |
ERROR_WRONG_METHOD | The method or action value is not one the API knows |
ERROR_BAD_PARAMETERS | A required parameter for that method is missing entirely |
ERROR_CAPTCHA_UNSOLVABLE | Submission worked, the solve did not. Poll-time, not submit-time |
CAPCHA_NOT_READY | Not an error. Keep polling the same ID |
Every code with its exact wording is listed in the API documentation.
Frequently asked questions
Should I retry after ERROR_GOOGLEKEY?
No. The same values will be rejected the same way every time, so a retry loop just wastes wall-clock time and hides the real problem. Fix the value, then submit once. Retrying is the correct response to a poll-time failure, not a submit-time one.
My sitekey is correct but still rejected. Now what?
Check for invisible damage first: a trailing newline from command substitution, quotes from a config file, or a truncated value from a fixed-width database column. Print the length of the string you are sending. A standard reCAPTCHA sitekey is 40 characters, so anything shorter has been clipped somewhere.
Does pageurl have to be publicly reachable?
No. It identifies the page the widget belongs to, so a page behind a login works fine. What it cannot be is a placeholder: sending a different domain than the one hosting the widget produces a token the site will reject even when the submit succeeds.
Do Turnstile solves ever return ERROR_GOOGLEKEY?
They should not, because the turnstile method never reads a googlekey parameter. Seeing it back from a Turnstile call almost always means method is still set to userrecaptcha from a copied request. Fix the method, then send sitekey rather than googlekey.
Summary
Treat error_googlekey and ERROR_PAGEURL as spelling mistakes rather than failures. No task was created, no attempt was made, and nothing needs retrying. Check the parameter name against the method, read the sitekey from the live DOM instead of a constant, and send a page URL with its scheme attached. Full parameter tables for every method are on the reCAPTCHA solver page.
Debugging this is cheap on an unlimited captcha solver that runs locally. Rejected submits burn no credit and consume no quota, so you can fire the same request twenty times while you narrow down which character is wrong.
