How to Implement CAPTCHA in HTML: A Simple Developer Guide

Adding a CAPTCHA to your HTML form is one of the most effective first lines of defense against bots, spam submissions, and automated abuse. Whether you're protecting a login page, a contact form, or a checkout flow, the right CAPTCHA — and the right implementation — can make a significant difference.
This guide walks you through adding captcha html code to your project step by step, covering four popular options: reCAPTCHA v2 (Checkbox and Invisible), Cloudflare Turnstile, Prosopo Procaptcha, and Altcha. For each one, you'll get a full explanation, working HTML code examples, and guidance on backend verification, local testing, and troubleshooting.
What You Need Before You Start
Every CAPTCHA solution typically works around the same core concept: you register your website with a provider, receive a captcha key pair (a public site key and a private secret key), and use those keys to embed the widget and verify responses.
Here's where to register each provider before you write a single line of HTML:
During registration, you'll be asked to provide your domain name. For local development, add localhost to the allowed domains list (more on this in the testing section).
reCAPTCHA v2 Checkbox — Step-by-Step
The classic checkbox ("I'm not a robot") is the most widely recognised captcha html widget. It displays a visible checkbox and, when needed, a visual image challenge.
Step 1: Register and get your captcha key
Go to google.com/recaptcha/admin and select reCAPTCHA v2 → "I'm not a robot" Checkbox. Add your domain (including localhost for local testing) and save. You'll receive a Site Key and a Secret Key.
Step 2: Add the reCAPTCHA API script
Place the following <script> tag in your HTML <head> or before the closing </body> tag:
<script src="https://www.google.com/recaptcha/api.js" async defer></script>The async and defer attributes ensure the script loads without blocking page rendering.
Step 3: Add the widget div to your form
Place the <div> element inside your form, just before the submit button. Replace YOUR_SITE_KEY with the site key you received.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Contact Form</title>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<form action="/submit" method="POST">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
<label for="message">Message:</label>
<textarea id="message" name="message" required></textarea>
<!-- reCAPTCHA v2 Checkbox widget -->
<div class="g-recaptcha" data-sitekey="YOUR_SITE_KEY"></div>
<button type="submit">Send</button>
</form>
</body>
</html>Step 4: What happens on submission
When the user completes the challenge and submits the form, Google's script automatically appends a hidden input field named g-recaptcha-response to your form data. Your backend reads this token and sends it to Google's verification API along with your secret key.
reCAPTCHA v2 Invisible — Step-by-Step
The Invisible reCAPTCHA removes the visible checkbox entirely. Instead, it runs a risk analysis in the background and only triggers a visual challenge if suspicious behaviour is detected — resulting in a smoother UX for most users.
Step 1: Register with the correct type
In the Google reCAPTCHA admin console, select reCAPTCHA v2 → Invisible reCAPTCHA badge. This generates a different site key from the checkbox variant — do not reuse keys between types.
Step 2: Load the API script
Use the same script tag as the checkbox variant:
<script src="https://www.google.com/recaptcha/api.js" async defer></script>Step 3: Attach the CAPTCHA to your submit button
Instead of a separate <div>, you apply CAPTCHA attributes directly to the submit <button>, specifying a JavaScript callback to run once the challenge completes.
Define a callback function that programmatically submits the form after verification passes.
Full HTML form example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sign Up</title>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<form id="signup-form" action="/signup" method="POST">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<!-- Invisible reCAPTCHA: attached to the button -->
<button
class="g-recaptcha"
data-sitekey="YOUR_SITE_KEY"
data-callback="onSubmit"
data-size="invisible"
type="submit">
Create Account
</button>
</form>
<script>
function onSubmit(token) {
document.getElementById("signup-form").submit();
}
</script>
</body>
</html>When the button is clicked, reCAPTCHA runs its background analysis, calls onSubmit(token) with a response token, and your callback submits the form. The token is included as g-recaptcha-response in the POST body.
Cloudflare Turnstile — Step-by-Step
Cloudflare Turnstile is a privacy-first CAPTCHA alternative that does not track users, set profiling cookies, or display image puzzles. It offers three widget modes:
- Managed: The widget decides whether to show a challenge based on browser signals)
- Non-interactive: Passes without requiring user interaction for most visitors
- Invisible: Runs entirely in the background
Step 1: Create a widget in the Cloudflare Dashboard
Log in to your Cloudflare account, navigate to Application security → Turnstile, click Add widget, enter your domain, and select a widget type. Copy the Site Key and note your Secret Key.
Step 2: Add the Turnstile script
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>Step 3: Add the widget div to your form
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>Turnstile automatically injects a hidden input named cf-turnstile-response into the form. No extra JavaScript is required for standard form submission.
Full HTML form example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Login</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
</head>
<body>
<form action="/login" method="POST">
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<!-- Cloudflare Turnstile widget -->
<div class="cf-turnstile" data-sitekey="YOUR_SITE_KEY"></div>
<button type="submit">Log In</button>
</form>
</body>
</html>Turnstile's Managed mode will determine — based on browser signals — whether to show an interaction or pass silently. For testing, Cloudflare provides special dummy site keys (covered in the testing section).
Prosopo Procaptcha — Step-by-Step
Prosopo Procaptcha is a Web3-native, privacy-focused CAPTCHA designed as a direct drop-in replacement for reCAPTCHA. It does not rely on Google's infrastructure and is built with user privacy as a core design principle.
Step 1: Get your captcha key from Prosopo
Register at prosopo.io and create a new site to receive your Site Key.
Step 2: Load the Procaptcha bundle
Add the Procaptcha script:
<script src="https://js.prosopo.io/js/procaptcha.bundle.js" async defer></script>Step 3: Add the widget to your form
Place a <div> with the class procaptcha and your site key as the data-sitekey attribute.
Full HTML form example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Register</title>
<script type="module" src="https://js.prosopo.io/js/procaptcha.bundle.js" async defer></script>
</head>
<body>
<form action="/register" method="POST">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
<!-- Prosopo Procaptcha widget -->
<div class="procaptcha" data-sitekey="YOUR_SITE_KEY"></div>
<button type="submit">Register</button>
</form>
</body>
</html>Step 4: Token submission
On form submit, Procaptcha injects a hidden field named procaptcha-response into your form. Pass this token to Prosopo's verification API with your secret key on the backend.
Altcha — Step-by-Step
Altcha is a fully open-source, self-hosted CAPTCHA that uses a proof-of-work mechanism — the user's browser performs a small computational task instead of solving a visual puzzle. It collects no user data, sets no tracking cookies, and is fully GDPR-compliant. Because it is self-hosted, there is no third-party dependency.
Step 1: Load the Altcha widget
No registration is required. Load the custom element script:
<script async defer type="module"
src="https://cdn.jsdelivr.net/gh/altcha-org/altcha/dist/altcha.min.js">
</script>Step 2: Set up the challenge endpoint
Altcha requires a server-side endpoint that generates a cryptographic challenge (using an HMAC secret key you define). This endpoint is called by the widget before submission to fetch a fresh challenge. Your framework's Altcha server library handles this. The endpoint URL is passed to the widget via the challengeurl attribute.
Step 3: Add the widget to your HTML form
Full HTML form example
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Feedback</title>
<script async defer type="module"
src="https://cdn.jsdelivr.net/gh/altcha-org/altcha/dist/altcha.min.js">
</script>
</head>
<body>
<form action="/feedback" method="POST">
<label for="feedback">Your feedback:</label>
<textarea id="feedback" name="feedback" required></textarea>
<!-- Altcha widget -- points to your own challenge endpoint -->
<altcha-widget challengeurl="/api/altcha-challenge"></altcha-widget>
<button type="submit">Submit</button>
</form>
</body>
</html>Step 4: Token submission
After the proof-of-work completes, Altcha populates a hidden input named altcha with a base64-encoded payload. Your backend verifies this payload against your HMAC secret — no external API call is needed.
Backend Verification — How It Works
Regardless of which CAPTCHA you use, the server-side verification pattern is the same:
- Extract the token from the POST request body using the field name specific to each provider (g-recaptcha-response, cf-turnstile-response, procaptcha-response, or altcha).
- Send the token to the provider's verification endpoint — a POST request that includes your secret key and the user's token (and optionally the user's IP address for reCAPTCHA).
- Parse the JSON response — providers return a success/failure indicator in their response. reCAPTCHA and Turnstile use a success: true/false field; Prosopo uses verified: true/false; Altcha is verified locally against your HMAC secret. Some providers also return a hostname and challenge timestamp.
- Accept or reject the submission — if verification fails, return an error to the user and do not process the form data. If it passes, proceed normally.
Verification endpoints by provider:
- reCAPTCHA v2: https://www.google.com/recaptcha/api/siteverify
- Cloudflare Turnstile: https://challenges.cloudflare.com/turnstile/v0/siteverify
- Prosopo: https://api.prosopo.io/siteverify
- Altcha: Local — verified against your own HMAC secret, no external call needed
⚠️ Never use your site key for backend verification — always use the secret key. Mixing them up is one of the most common integration errors.
Testing Your CAPTCHA HTML on a Local Server
Testing CAPTCHA locally requires a few extra steps since most providers validate domain names.
- reCAPTCHA v2: Add localhost to the list of allowed domains in the Google reCAPTCHA admin console. The widget will render and function normally on http://localhost.
- Cloudflare Turnstile: Use Cloudflare's official test site keys. The key 1x00000000000000000000AA always produces a passing result; 2x00000000000000000000AB always fails. You can also pair the passing site key (1x00000000000000000000AA) with secret key 3x0000000000000000000000000000000AA on the backend to simulate a "timeout-or-duplicate" error. Swap all test keys for your real production keys before deploying.
- Prosopo Procaptcha: localhost is supported out of the box during development — no extra configuration needed.
- Altcha: Because it is self-hosted, localhost works natively. Just make sure your challenge endpoint is also running locally.
Use your browser's DevTools → Network tab to confirm the CAPTCHA API script loads (HTTP 200) and that your form POST includes the expected token field.
Troubleshooting Common Problems
CAPTCHA widget does not render
- Double-check that the site key matches the CAPTCHA type you registered (checkbox keys won't work for invisible, and vice versa).
- Confirm the API <script> tag is present and loads without a 404 or CORS error in DevTools.
- Ensure the domain in your registration matches the domain you're serving from, including whether www. is present or not.
Token is missing from the form POST
- The hidden input is only injected after the challenge completes. If the user submits before it's ready, no token is sent.
- For Invisible reCAPTCHA, make sure the onSubmit callback triggers form.submit() after the token is available, not before.
- For Altcha, confirm your challenge endpoint is reachable and returning a valid challenge.
Backend verification returns a failure response
- Verify you are sending the secret key (not the site key) to the verification endpoint.
- Tokens are typically single-use — if you're retrying the same POST in testing, generate a fresh token each time.
- Check for clock skew: some providers (Altcha especially) reject challenges that are too old.
"Invalid domain" or sitekey mismatch error
- On localhost, ensure localhost (not 127.0.0.1) is in your allowed domains list.
- For Turnstile, switch to the test site key during local development.
Widget hidden or overlapping other elements
- CAPTCHA widgets render in iframes with a fixed z-index. If another element (a modal overlay, sticky header, etc.) has a higher z-index, it can obscure the widget.
- Set an explicit z-index on the parent container, or check for overflow: hidden on ancestor elements cutting off the iframe.
Accessibility note: All four providers in this guide offer accessible alternatives. reCAPTCHA and Turnstile provide audio challenges; Prosopo and Altcha are designed to be frictionless by default, reducing reliance on visual puzzles entirely.
Automating & Testing CAPTCHA solving with CapMonster Cloud
Once your CAPTCHA implementation is live, you may need to test it programmatically — for QA pipelines, integration tests, or automated monitoring. Manually solving CAPTCHAs every time a test runs is not realistic at scale.
CapMonster Cloud is an AI-powered automated CAPTCHA solving service that supports all four CAPTCHA types covered in this guide: reCAPTCHA v2 (checkbox and invisible), Cloudflare Turnstile, Prosopo Procaptcha, and Altcha. It works by exposing a simple API: you send a task with your target page URL and site key, and the service returns a solved token ready to inject into your form.
How it fits into a developer workflow:
- Automated integration testing: Run end-to-end tests on CAPTCHA-protected forms in CI/CD pipelines without manual interaction.
- QA verification: Confirm that your backend correctly validates and rejects tokens under different conditions.
- Data collection pipelines where interaction with CAPTCHA-protected pages is part of an authorised workflow.
CapMonster Cloud is compatible with the widely used API interface, which means it integrates with many existing automation frameworks with minimal configuration. You submit a task containing the page URL and site key, poll for the result, and receive the token — which you inject as the appropriate hidden field value in your form.
Conclusion
Implementing a captcha html solution doesn't require complex infrastructure — just a script tag, a widget element, the right captcha key pair, and a server-side token check. Each option in this guide has its own strengths: reCAPTCHA v2 is battle-tested and widely trusted; Turnstile is frictionless and privacy-friendly; Prosopo offers a Web3-native alternative without Google dependency; and Altcha gives you full control with self-hosted, open-source infrastructure.
Ready to get started? Pick the CAPTCHA that fits your privacy, UX, and infrastructure requirements, register your site key, and use the code examples above to drop it into your form in minutes. If you need to automate testing or QA on your protected forms, CapMonster Cloud supports all four providers and integrates directly into your existing pipelines.






