Results of adding custom reCAPTCHA v2 to forms are hit-or-miss

I’m trying to add reCAPTCHA V2 to all of the netlify forms on our HUGO-based website. (Note, I’m not the owner, just a lowly dev here.)

It seems like it’s definitely working sometimes, but then other times I get apparent errors. I think my test results (doing the same thing on the same pages) seems to produce one of 4 different results:

  1. I am not challenged by reCAPTCHA and I am immediately redirected to the form-success page. I think it is probably working as intended, since if reCAPTCHA doesn’t see me as suspicious, then I shouldn’t be challenged.
  2. I am challenged and I either succeed and get send to the form-success page, or I fail and am stopped from submitting the form at all. This also seems fine.
  3. I am not prompted and I sent straight to the form-success page, but it’s status 500 and I just get an error instead of seeing the page (clicking refresh or navigating to the page manually works every time though). In the case of number three my form data looks like this when I inspect the POST in dev tools:
form-name: contact
g-recaptcha-response: 
txtName: Dan Testing12754
txtEmail: test@net.test
file: (binary)
bot-field: 
txtMsg: This is just a test on a deploy preview.
btnSubmit: Send Message

And here is the request headers:

:authority: deploy-preview-1414--stagingjbm.netlify.app
:method: POST
:path: /form-success/
:scheme: https
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
accept-encoding: gzip, deflate, br
accept-language: en-US,en;q=0.9
cache-control: max-age=0
content-length: 955
content-type: multipart/form-data; boundary=----WebKitFormBoundaryx5AT96aBkAHZmNAa
cookie: cart_jaquish=%5B%5D; _ga=GA1.2.1172870204.1600901590; _gid=GA1.2.41866906.1600901590; _omappvp=gLYfMkAOSndZYHwHqFCkm6W61uMu4DZiWnSDRNZMGqJTIeG0e1Mc5sqpFn2XR5lSjIVnbbIxsRDvTF9XUyYlWlvyhBNk2iUo; _omappvs=1600901589600; tracker_device=fd7f1332-8289-497a-b9d1-d6d0de24a5c4; _fbp=fb.1.1600901589914.519975691
origin: https://deploy-preview-1414--stagingjbm.netlify.app
referer: https://deploy-preview-1414--stagingjbm.netlify.app/contact/
sec-fetch-dest: document
sec-fetch-mode: navigate
sec-fetch-site: same-origin
sec-fetch-user: ?1
upgrade-insecure-requests: 1
user-agent: Googlebot/2.1

The response in turn is blank with status 500.

  1. I am challenged and I think succeed, but then the I still get a status 500 on the ‘form-success’ page
    Here is a sample of form data submitted, as recorded by dev tools for one of these instances:
form-name: newsletter
bot-field: 
email: test@foo.bar
title: Jaquish Biomedical
relpermalink: /
g-recaptcha-response: 03AGdBq26BYx9ATqr17AX6YXGz3xrh-131NNnmFZGxOrajQW04wmNnC2zDIRATBaOF7dNvfJ-ONB9dJCfN9HafuCVGASNIdAA44Yho0QMoCwDRDpbpz0nHJwENI2dF9KdZDmB9p_R3SODSZVUhbysSKoeTyQM7BzznYElc1ztghi3v9Sakozefbn0vEJYhtZeoESPevkKlMxQwD_giX26Uz_cE8ptP9ozqadFCQ9aBJuOSGocBsXCmW_-TxO8EGD-hRwy0tMqJP35xbTa53kXPCXN72aosv_ihzsTl6gu3aRh84SE59o2QWVOoVrE0pyf8D9JMeD5lM1oFB89tZQf_VgwcqxZClS8NcKAi2lVtsXoHaYUkD6pjjjGp0XDOJI_gW2FAb4jTAxBS2YE0d5DJEr74WKYjr9EBfuNqhnwtW2XkMkFUbf82Eq0jq1jUzHb3KdsdGwudmmRd0JcgEwmyNtfQk8JMReGdXzEOFEtYezQrr-neIStFd24

As you can see in this one, there is a valid captcha key, but still got status 500. The request headers for this one were identical to number 3.


It’s these number three and four cases that worry me. I don’t expect a form submit to work with a missing g-recaptcha-response, of course. However, from reading other posts and documentation I’m given to understand that I should receive a response with a status 303 for missing g-recaptcha-response. So getting a status 500 seems like a Netlify error, especially when the form submit should be a success. I’m not seeing any relevant console errors or warnings along with any of this either.

Here is a link to the deploy preview that I’m using right now:
https://deploy-preview-1414--stagingjbm.netlify.app/
Note that every page has a newsletter sign-up form in the footer. This usually the form I use for testing. Although reCAPTCHA should be hooking into all the forms on the entire site, so any form should work.

I’ve been opening chrome with a guest window in order to get reCAPTCHA to trigger, and when that doesn’t work, I used device emulation in dev tools with a custom user agent set to Googlebot/2.1.

Here is the code I’m loading in the <head> in order to dynamically generate the reCAPTCHA element for all forms on a given page:

<script src="https://www.google.com/recaptcha/api.js" async defer></script>
<script defer>
  // Set up callback function for submitting form after reCAPTCHA verification.
  // We're setting and retrieving formToSubmit dynamically so that we know which of multiple forms to submit on a page
  window.formToSubmit = null;
  function submitActiveForm() {
    try {
      document.getElementsByClassName(window.formToSubmit)[0].submit();
    } catch (e) {
      console.error("Failed to submit form due to the following error");
      console.error(e);
    }
  };
  window.submitActiveForm = submitActiveForm; // Make sure the callback is in the global namespace

  window.addEventListener("DOMContentLoaded", () => {

    // Grab all forms on the page that are set up to submit via Netlify and use reCAPTCHA
    const allForms = document.querySelectorAll("[data-netlify-recaptcha=true]");
    let formCount = 0; // Used to provide a unique identifier to each form

    // Set up reCAPTCHA for each form on the page
    allForms.forEach((form) => {

      // Add identifier to form
      formCount++;
      let formUniqueClass = `recaptcha-form-number-${formCount}`;
      form.classList.add(formUniqueClass);
      
      // Set up globally accessible function handle with bound context
      window[`form${formCount}PreSubmit`] = (event) => {
        window.formToSubmit = formUniqueClass;
        event.preventDefault();
        event.stopImmediatePropagation();
        grecaptcha.execute();
      };

      // Create special google reCaptcha element to form
      let reCaptchaDiv = document.createElement("div");
      reCaptchaDiv.dataset.sitekey = {{.Site.Params.SITE_RECAPTCHA_KEY}}; // Google Recaptcha Public Site Key
      reCaptchaDiv.dataset.size = "invisible"; // Required boilerplate
      reCaptchaDiv.classList.add("g-recaptcha"); // Required boilerplate
      reCaptchaDiv.dataset.callback = "submitActiveForm"; // Name of callback function as a string

      // Append invisible reCAPTCHA element to form as second to last element
      form.insertBefore(
        reCaptchaDiv,
        form.children[form.children.length - 1]
      );

      // Set up intercept for initial submit action and render reCAPTCHA element (if user is suspicious)
      form.addEventListener("submit", window[`form${formCount}PreSubmit`]);
    });

    // Report that we're finished setting up reCAPTCHA listeners so that events.js can set up its listeners
    window.reCaptchaReadyStatus = true;
    const captchaReadyEvent = new Event("reCaptchaReady");
    document.dispatchEvent(captchaReadyEvent);
  });
</script>

Thanks for any help you may be able to provide!

Update: I deployed this to our development site and stopped getting status 500 returns from the server and failed captchas started returning status 303 and not redirecting to the success page. So, my theory right now is just the captcha + deploy preview = server error.

Hey @LandGod,
Thanks so much for sharing these notes on your investigation- super helpful and appreciated :trophy: I am able to reproduce, if not quite the 303/500, then at least the captcha fail on a Deploy Preview:

I think this is because the recaptcha is bound to a specific domain on the Google side. So, at least for me, if I add the Deploy Preview domain (deploy-preview-6--nostalgic-payne-051c14.netlify.app) in my recaptcha admin dashboard, then everything works properly again… for that specific Deploy Preview. But the exact same deploy at the deploy URL (5f77943e6b886900078c07e7--nostalgic-payne-051c14.netlify.app) continues to be broken.

This also seems to be true for the Netlify-provided recaptcha (in addition to the custom one we’ve been talking about).

I’ll flag this for our docs team to take a look at. Thanks again for all your research here. Let us know if you need anything else at this point.

1 Like

Just question about how netlify handles token verification.
Google requires 2 parameters in post request to https://www.google.com/recaptcha/api/siteverify. Does netlify sends 3rd optional parameter called remoteip? Can this be configured? Dont want to waste lambda function just to handle that one parameter.

Hey @leomrav,
Which recaptcha are you using? Could you share your form code and we can tell you if you’re missing anything? We support checkbox v2 and invisible v2 with minimal configuration on your end. You can read more here:

Let us know if that helps or if you have follow-up questions!

Hi @jen, recaptcha works fine but I am wondering does netlify sends user IP adress in post request when user response token verification happens and can it be configured. see Verifying the user's response  |  reCAPTCHA  |  Google Developers

Hey! Are you talking about a POST to us or a POST to the reCAPTCHA service?

To the reCAPTCHA service. Post request to https://www.google.com/recaptcha/api/siteverify. There is one optional parameter called remoteip for user IP address. I need to configure that so I am wondering is it possible.

We do capture the submission IP, as discussed here: