83

I am setting an invisible reCAPTCHA in my web application and having trouble verifying the user's response. (even though I am passing the correct POST parameters)

I am programmatically invoking the challenge by calling grecaptcha.execute(); on the client-side. And submitting the form afterwards (registrationForm.submit();) using the recaptcha callback:

<div class="g-recaptcha"
  data-sitekey="SITE_KEY"
  data-callback="onSubmit"
  data-size="invisible">
</div>

Now after reading "Verifying the user's response" documentation, I figured that the response token is passed as a POST parameter to g-recaptcha-response:

For web users, you can get the user’s response token in one of three ways:

  • g-recaptcha-response POST parameter when the user submits the form on your site
  • ...

So I am using Fetch to create a POST request on the server side to the verification endpoint with the required body data:

verify(req, res, next) {
  const VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify";

  return fetch(VERIFY_URL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      secret:   process.env.RECAP_INVIS_SECRET_KEY,
      response: req.body['g-recaptcha-response'],
    }),
  })
  .then(response => response.json())
  .then(data => {
    res.locals.recaptcha = data;
    return next();
  });
}

But I keep getting the following response:

{ success: false, error-codes: [ 'missing-input-response', 'missing-input-secret' ] }

Even though I am passing the response and secret as JSON data in the POST body.

Am I doing something wrong? Regards.

3 Answers 3

221

Doing a bit of research and digging around the reCaptcha Google forums, It seems that this endpoint only accepts the default content type; application/x-www-form-urlencoded.

Which means you should not use JSON to send your response token and site key. Instead, send the value as how the application/x-www-form-urlencoded defined:

Forms submitted with this content type must be encoded as follows:

  1. Control names and values are escaped. Space characters are replaced by '+', and then reserved characters are escaped as described in [RFC1738], section 2.2: Non-alphanumeric characters are replaced by '%HH', a percent sign and two hexadecimal digits representing the ASCII code of the character. Line breaks are represented as "CR LF" pairs (i.e., '%0D%0A').
  2. The control names/values are listed in the order they appear in the document. The name is separated from the value by '=' and name/value pairs are separated from each other by '&'.

Therefore, you got two ways of doing this, either by passing the POST parameters through the URL (query strings) and sending it as a POST request:

https://www.google.com/recaptcha/api/siteverify?secret=${SECRET_KEY}&response=${req.body['g-recaptcha-response']}

or appending the data to the body manually like so:

verify(req, res, next) {
  const VERIFY_URL = "https://www.google.com/recaptcha/api/siteverify";

  return fetch(VERIFY_URL, {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: `secret=${SECRET_KEY}&response=${req.body['g-recaptcha-response']}`,
  })
  .then(response => response.json())
  .then(data => {
    res.locals.recaptcha = data;
    return next();
  });
}

The official Google documentation can be found here:
Recaptcha - Verifying the user's response.

8
  • 2
    Thank you! The POST parameters solution worked for me. Commented Jun 20, 2019 at 12:12
  • 3
    Excellent. This worked for me as well. If you are using axios, this is very helpful: github.com/axios/…
    – saurabhj
    Commented Jan 23, 2021 at 5:46
  • 3
    Even assuming that Google makes sense of this behavior (?!), There is no information in Google's documentation, not even now.
    – dovid
    Commented Apr 6, 2021 at 16:32
  • 2
    The documentation shows what the API request accepts for it's POST parameters. However, only the content type is not specified which is application/x-www-form-urlencoded from what we can figure out. I have included a link to the official Google documentation for the verify request so future users can be informed of any changes. (Thanks!)
    – u-ways
    Commented Apr 8, 2021 at 16:14
  • 1
    This is no sense, the API returns in JSON, but the parameters NEED be in form-urlencoded... Congrats to Google... Commented Oct 31, 2022 at 14:52
3

Extending on U-ways answer above (thanks for that), if you wanted to keep a JS object structure instead of formatting inline params, you could make use of URLSearchParams and Object.entries:

const recaptchaBody = {
  secret: RECAPTCHA_SECRET,
  response: recaptchaResponse,
};

// Check recaptcha validity
fetch("https://www.google.com/recaptcha/api/siteverify", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams(Object.entries(recaptchaBody)).toString(),
})
.then((res) => res.json())
.then((data) => {
  // If it's not a success..
  if (!data?.success) {
    // ERROR
  }
});
1
  • Encoding it safely is required, unless we always assume that the parameter values are url-safe.
    – Old Geezer
    Commented Feb 27, 2023 at 4:17
0

I've got same issue with Turnstile CloudFlare captcha, and somehow the internet led me to this very SO page.

But the above anwsers helped me a lot and in my case this was a missing value in header

'Content-Type': 'application/json'

Not the answer you're looking for? Browse other questions tagged or ask your own question.