How to Fix ERROR_GOOGLEKEY and ERROR_PAGEURL on Submit

error_googlekey - 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

CodeDocumented meaningWhich parameter
ERROR_GOOGLEKEYInvalid googlekey parameterThe sitekey you read off the page
ERROR_PAGEURLInvalid pageurl parameterThe 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.

MethodKey parameterWrong name gives you
userrecaptchagooglekeyERROR_GOOGLEKEY
turnstilesitekeyERROR_BAD_PARAMETERS
geetestgt plus challengeERROR_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 in id, name or the iframe’s title.
  • 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/login is not a URL. Send https://example.com/login.
  • A relative path. /login is 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

  1. Print the exact request body you are sending. Not the variables, the body.
  2. Check the parameter name against the method: googlekey for reCAPTCHA, sitekey for Turnstile.
  3. Confirm the value is non-empty and has no trailing newline.
  4. Re-read the sitekey from the live page and compare it to the one you sent.
  5. Confirm pageurl starts with http:// or https://.
  6. 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

CodeMeans
ERROR_WRONG_USER_KEYThe API key is missing or empty, checked before either of these
ERROR_WRONG_METHODThe method or action value is not one the API knows
ERROR_BAD_PARAMETERSA required parameter for that method is missing entirely
ERROR_CAPTCHA_UNSOLVABLESubmission worked, the solve did not. Poll-time, not submit-time
CAPCHA_NOT_READYNot 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.