Form Submissions Not Showing on Netlify Dashboard (with reCAPTCHA V3 integration)

Description of Issue:
I’m experiencing an issue where form submissions are not showing in the Netlify Forms dashboard despite the form being detected correctly during deployment. I’ve followed the Netlify Forms documentation and ensured the form meets all criteria. However, after submitting the forms I am faced with a ‘303’ response. Which I looked through forum posts for but was not able to find anything that helped. Been at this for almost 6 hours.

Here is my HTML Code for the form:

<form name="contact" method="post" action="/" id="consultation-form" data-netlify="true" netlify>
                <input type="hidden" name="form-name" value="contact" />
                <input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response" />
                <div class="row mb-3">
                  <div class="col-12 col-md-6">
                    <label for="firstName" class="form-label">First Name</label>
                    <input type="text" class="form-control" id="firstName" placeholder="First Name" name="First Name" required />
                  </div>
                  <div class="col-12 col-md-6 mt-3 mt-md-0">
                    <label for="lastName" class="form-label">Last Name</label>
                    <input type="text" class="form-control" id="lastName" placeholder="Last Name" name="Last Name" />
                  </div>
                </div>
                <div class="mb-3">
                  <label for="email" class="form-label">Email</label>
                  <input type="email" class="form-control" id="email" placeholder="Email" name="Email" required />
                </div>
                <div class="mb-3">
                  <label for="message" class="form-label">Message</label>
                  <textarea class="form-control" id="message" rows="3" placeholder="Message" name="Message" required></textarea>
                </div>
                <button type="submit" class="btn btn-submit">Submit</button>
              </form>

I’m using JavaScript to handle reCAPTCHA v3 validation and form submission. Here is my script:

const form = document.getElementById("consultation-form");

const handleSubmit = async (event) => {
  event.preventDefault(); // Prevent form submission from reloading the page

  grecaptcha.ready(async () => {
    try {
      // Execute reCAPTCHA and get the token
      const token = await grecaptcha.execute("6LfVtpIqAAAAAAFtdzD58iQvvwxhNVzz_DayrQ8Z", { action: "submit" });

      // Attach the token to the hidden input field
      document.getElementById("g-recaptcha-response").value = token;

      // Collect form data
      const formData = new FormData(form);
      const formObject = Object.fromEntries(formData.entries());

      // Submit the data to the Netlify function for validation
      const response = await fetch("/.netlify/functions/validate-recaptcha", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(formObject),
      });

      const result = await response.json();

      if (result.success && result.score > 0.5) {
        // If reCAPTCHA validation passes, submit the form to Netlify
        await fetch("/", {
          method: "POST",
          headers: { "Content-Type": "application/x-www-form-urlencoded" },
          body: new URLSearchParams(formData).toString(),
        });

        // Show the success section
        document.getElementById("contact-form").classList.add("d-none");
        document.getElementById("success-section").classList.remove("d-none");
      } else {
        alert("Failed reCAPTCHA verification. Please try again.");
      }
    } catch (error) {
      console.error("Error during submission:", error);
      alert("An error occurred. Please try again later.");
    }
  });
};

form.addEventListener("submit", handleSubmit);

Here is my serverless function for validation:

import fetch from "node-fetch";

export async function handler(event) {
  if (event.httpMethod !== "POST") {
    return {
      statusCode: 405,
      body: JSON.stringify({ success: false, message: "Method not allowed" }),
    };
  }

  const { "g-recaptcha-response": token } = JSON.parse(event.body);

  if (!token) {
    return {
      statusCode: 400,
      body: JSON.stringify({ success: false, message: "Missing reCAPTCHA token" }),
    };
  }

  const secretKey = process.env.RECAPTCHA_SECRET; // Securely load secret key from Netlify environment variables

  const response = await fetch(`https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${token}`, { method: "POST" });

  const verificationResult = await response.json();

  if (verificationResult.success && verificationResult.score > 0.5) {
    return {
      statusCode: 200,
      body: JSON.stringify({ success: true, score: verificationResult.score }),
    };
  } else {
    return {
      statusCode: 400,
      body: JSON.stringify({
        success: false,
        message: "reCAPTCHA verification failed",
        errorCodes: verificationResult["error-codes"],
      }),
    };
  }
}

Observations:
The form is detected in the Netlify Forms dashboard after deployment.
Submissions are not appearing in the dashboard, even after successful POST requests.

Troubleshooting Attempts:
Verified that all input fields have unique name attributes matching the HTML form.
Ensured the action=“/” attribute is present in the tag.
Confirmed that the form includes data-netlify=“true” and a hidden form-name field.
Tested locally using netlify dev --context production and on the production site hosted on Netlify.
Checked spam submissions in the Netlify Forms dashboard.

Questions for Support:
Are there any specific reasons why the submissions might not be showing despite correct form detection and payloads?
Could the reCAPTCHA validation workflow interfere with Netlify’s form handling, and how should this be resolved?
Does the 303 response indicate a misconfiguration in the form’s action or submission process?
Are there additional debugging steps or logs I can review to identify the issue?

Deployment Info:
Site URL: https://medenditactus.netlify.app
Form Name: contact
No framework (HTML, CSS, JS)
reCAPTCHA v3 with Netlify serverless validation

Any help will be appreciated aswell as any suggestions both with my implementation and potential fixes to my issue.

@YunYun5 I can’t see any Netlify documentation that specifically mentions reCAPTCHA v3.

However looking at this old post:
https://answers.netlify.com/t/can-we-add-recaptcha-v3-in-in-our-website-i-am-using-free-version-of-netlify/48496

The answer says:

Which for a lack of anything else, I’m interpreting to be this:
https://docs.netlify.com/forms/spam-filters/#custom-recaptcha-2

Looking at that, then looking at what you’ve provided above, and the code on your site…
I can’t see any reference to data-netlify-recaptcha="true"
So it’s possible that it’s required, and you’re missing it.

Try running through the ‘Custom reCAPTCHA’ documentation again.

@nathanmartin just added the data-netlify-recaptcha=“true” to the forms attributes and redeployed. It did not change the outcome.

I implemented a solution that separates reCAPTCHA validation and Netlify Forms submission. The reCAPTCHA token (g-recaptcha-response) is dynamically added and validated via a Netlify Function without including it in the form submission payload. After successful validation, the form data is manually submitted to Netlify without the g-recaptcha-response field, ensuring it matches the fields defined in the HTML.

HTML Code no need for g-recaptcha-response because we are going to add it dynamically:

<form name="contact" method="post" action="/" id="consultation-form" data-netlify="true">
                <input type="hidden" name="form-name" value="contact" />
                <!-- <input type="hidden" id="g-recaptcha-response" name="g-recaptcha-response" /> -->

JS:

const form = document.getElementById("consultation-form");

const handleSubmit = async (event) => {
  event.preventDefault(); // Prevent default form submission behavior

  try {
    // Execute reCAPTCHA and get the token
    const token = await grecaptcha.execute("6LfVtpIqAAAAAAFtdzD58iQvvwxhNVzz_DayrQ8Z", { action: "submit" });

    // Dynamically add the reCAPTCHA token to the form
    const recaptchaInput = document.createElement("input");
    recaptchaInput.setAttribute("type", "hidden");
    recaptchaInput.setAttribute("name", "g-recaptcha-response");
    recaptchaInput.setAttribute("value", token);
    form.appendChild(recaptchaInput);

    // Validate reCAPTCHA via Netlify Function
    const recaptchaResponse = await fetch("/.netlify/functions/validate-recaptcha", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ "g-recaptcha-response": token }),
    });

    const recaptchaResult = await recaptchaResponse.json();

    if (recaptchaResult.success && recaptchaResult.score > 0.5) {
      // If reCAPTCHA is valid, remove the token field before submitting
      recaptchaInput.remove();

      // Submit the form to Netlify
      await fetch("/", {
        method: "POST",
        headers: { "Content-Type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams(new FormData(form)).toString(),
      });

      // Show success message and hide the form
      document.getElementById("contact-form").classList.add("d-none");
      document.getElementById("success-section").classList.remove("d-none");
    } else {
      alert("Failed reCAPTCHA verification. Please try again.");
    }
  } catch (error) {
    console.error("Error during form submission:", error);
    alert("An error occurred. Please try again later.");
  }
};

form.addEventListener("submit", handleSubmit);

Serverless Function:

import fetch from "node-fetch";

export async function handler(event) {
  if (event.httpMethod !== "POST") {
    return {
      statusCode: 405,
      body: JSON.stringify({ success: false, message: "Method Not Allowed" }),
    };
  }

  const { "g-recaptcha-response": token } = JSON.parse(event.body);

  if (!token) {
    return {
      statusCode: 400,
      body: JSON.stringify({ success: false, message: "Missing reCAPTCHA token" }),
    };
  }

  const secretKey = process.env.RECAPTCHA_SECRET; // Load secret key from environment variables

  const response = await fetch(`https://www.google.com/recaptcha/api/siteverify?secret=${secretKey}&response=${token}`, {
    method: "POST",
  });

  const verificationResult = await response.json();

  if (verificationResult.success && verificationResult.score > 0.5) {
    return {
      statusCode: 200,
      body: JSON.stringify({ success: true, score: verificationResult.score }),
    };
  } else {
    return {
      statusCode: 400,
      body: JSON.stringify({
        success: false,
        message: "reCAPTCHA verification failed",
        errorCodes: verificationResult["error-codes"],
      }),
    };
  }
}

This fixed my probem hope this helps someone else.

@YunYun5 Nice job on your own implementation!

My suggestion was a wild long shot, so I’m not surprised it didn’t work.

@hrishikesh With reCAPTCHA V3 seemingly being 6 years old at this point, could you communicate internally regarding updating the spam filters documentation. Even if Netlify don’t provide a built in solution, it’d be great to have a note regarding doing a custom implementation.

that would be something cool and very useful to have. It took me a couple hours and with reCAPTCHA V3 being so so so much better than V2 in regards to user satisfaction and security this owuld be an amazing documentaiton. Im sure you dont need it but if you want help I can walk through my implementation.

I think the only problem was if you send the g-recaptcha-response to netlify it invalidates the submission probably because it is expecting something else im not sure. But when you seperate the two (validation and netlify submisison) it works great. Also by not having g-recaptcha-response in the original HTML form I get to not have a g-recatpcha-response field in the netlify dashboard which is nice for contact form submissions.

The custom implementation would not be useful to prevent real spam. Currently, if you have a Netlify ReCaptcha, we would reject submissions without a valid g-recaptcha-response field. This is what would actually block spam.

In the custom implementation, you’re essentially offloading this to the client-side script, so if:

  • someone disables JavaScript on your site
  • or sends a submissions using curl or other API tools like Postman,

the implementation would be rendered useless and the submission would come through. I had come up with such implementation ideas before, but trashed the idea specifically due to the above pointers.

Now, it’s still possible to further complicate this and use Edge Functions to actually prevent spam while allowing server-side validation, but that seems like a really extra overhead and not sure if someone would actually want that. I’m happy to write a Support Guide or a blog post about it, but due to its complexity nature (and targetting a very specific use-case), I believe it’s not a good candidate for a docs update.

@hrishikesh I’m more concerned with a note in the docs that at least mentions reCAPTCHA V3.

It’d be perfectly fine if it just said that Netlify Forms doesn’t support reCAPTCHA V3.

It could point people to the potential for a custom solution, but as you’ve mentioned above, should draw their attention to the fact that they’d need to handle the validation via edge function (presumably on all routes) to protect the POST submissions from getting through.

Sure, I’ll add a note for the docs team to note that ReCaptcha v3 does not work.

I dont have a lot of experience with web development yet to know about common attacks. Are the situation/attacks bellow common?

  • someone disables JavaScript on your site
  • or sends a submissions using curl or other API tools like Postman,

If so should I not be using my implementation and use ReCaptcha V2 or something else instead what would be your suggestion.

@YunYun5 Tools can scrape the site to determine the existence of forms, what fields they consists of, and where they submit to etc

Once that information is gathered they can submit directly bypassing any/all client side validation/restrictions/security.

If you want the built in Netlify solution, it has to be reCAPTCHA V2.

Here’s a potential implementation with server-side validation: Add support for Cloudflare Turnsile (reCaptcha Alternative) - #10 by hrishikesh. You’d have to do something similar for ReCaptcha v3.