If you want to add captcha to react form safely, the short answer is: render the challenge on the client, capture a pass token, and verify that token on your server before accepting the submission. That keeps bots from posting directly to your endpoint while preserving a normal form flow for real users.
For React apps, the trick is not just “put a widget on the page.” It’s deciding when to show it, how to store the token, how to validate it server-side, and how to avoid making the form feel heavier than it needs to be. A good implementation is usually invisible until needed.
What a solid React CAPTCHA flow looks like
A modern React form integration should follow a simple trust boundary:
- The browser renders the form and a CAPTCHA challenge.
- The user completes the challenge and receives a short-lived
pass_token. - Your React app submits the form fields plus that token to your backend.
- Your backend verifies the token with the CAPTCHA provider before processing the form.
That last step is the one people sometimes skip, and it’s the most important. If you only check the token in the browser, an attacker can bypass your UI and post directly to your API.
Here’s the flow in plain terms:
- Client: gathers form values and CAPTCHA token
- Server: validates token with private credentials
- App logic: accepts or rejects the submission based on validation result

With CaptchaLa, the validation endpoint is server-side and uses X-App-Key plus X-App-Secret. The validation request goes to POST https://apiv1.captcha.la/v1/validate with a body like { pass_token, client_ip }. That makes it straightforward to keep the secret out of your React bundle.
How to add captcha to react form step by step
Below is a practical way to wire it up in a React app. The exact component names will depend on the SDK or loader you use, but the architecture stays the same.
1) Load the CAPTCHA script
CaptchaLa provides a loader at:
https://cdn.captcha-cdn.net/captchala-loader.js
You can load it once in your app shell or inject it in the page where the form lives. If you prefer a lighter framework-specific integration, CaptchaLa also supports native SDKs for Web, including React, plus iOS, Android, Flutter, and Electron.
2) Render the challenge in your form
In React, you generally want to keep CAPTCHA state separate from form state:
- form inputs: name, email, message, etc.
- captcha token: a small piece of transient state
- submission state: idle, submitting, success, error
A simple pattern is to disable the submit button until the CAPTCHA is completed, then pass the token along with the form payload.
import { useState } from "react";
export default function ContactForm() {
const [form, setForm] = useState({ name: "", email: "", message: "" });
const [captchaToken, setCaptchaToken] = useState("");
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState("");
async function handleSubmit(e) {
e.preventDefault();
setSubmitting(true);
setError("");
try {
const response = await fetch("/api/contact", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
...form,
pass_token: captchaToken,
}),
});
if (!response.ok) {
throw new Error("Submission failed");
}
// Reset form after success
setForm({ name: "", email: "", message: "" });
setCaptchaToken("");
} catch (err) {
setError("Please try again.");
} finally {
setSubmitting(false);
}
}
return (
<form onSubmit={handleSubmit}>
<input
value={form.name}
onChange={(e) => setForm({ ...form, name: e.target.value })}
placeholder="Name"
/>
<input
value={form.email}
onChange={(e) => setForm({ ...form, email: e.target.value })}
placeholder="Email"
/>
<textarea
value={form.message}
onChange={(e) => setForm({ ...form, message: e.target.value })}
placeholder="Message"
/>
{/* Render your CAPTCHA widget here and store its pass token */}
<div id="captcha-container" />
<button type="submit" disabled={submitting || !captchaToken}>
{submitting ? "Sending..." : "Send"}
</button>
{error ? <p>{error}</p> : null}
</form>
);
}The important part is not the widget shell itself; it’s the token handoff. Your CAPTCHA component or loader should expose a success callback that provides the token, which you then keep in component state or a ref until submission.
3) Validate the token on the backend
Your React app should never call the validation endpoint directly if it requires secrets. Instead, send the token to your API route or server, then validate it there.
CaptchaLa’s server validation endpoint expects:
POST https://apiv1.captcha.la/v1/validate- body:
{ pass_token, client_ip } - headers:
X-App-KeyandX-App-Secret
A minimal Node-style server route might look like this:
export async function postContact(req, res) {
const { name, email, message, pass_token } = req.body;
const client_ip = req.headers["x-forwarded-for"]?.split(",")[0] || req.socket.remoteAddress;
// Validate captcha before processing the form
const captchaResponse = await fetch("https://apiv1.captcha.la/v1/validate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-App-Key": process.env.CAPTCHALA_APP_KEY,
"X-App-Secret": process.env.CAPTCHALA_APP_SECRET,
},
body: JSON.stringify({
pass_token,
client_ip,
}),
});
const result = await captchaResponse.json();
if (!captchaResponse.ok || !result?.success) {
return res.status(403).json({ error: "Captcha validation failed" });
}
// Continue with your normal form handling here
return res.status(200).json({ ok: true });
}A few technical details matter here:
- Validate before any expensive work, like sending email or writing to your CRM.
- Treat a failed CAPTCHA as an authorization failure, not a frontend error.
- Include the client IP when possible, because it can strengthen the validation context.
- Keep
X-App-Secreton the server only. - Expire or discard the token after one successful use.
4) Decide when to challenge
You don’t need to show a challenge on every form interaction. Many apps use risk-based triggering:
- show CAPTCHA only after suspicious behavior
- show it after failed attempts
- show it on sensitive actions like account creation, password reset, or lead submission
- keep it visible for high-abuse endpoints
That approach can preserve form completion rates while still reducing automation. It also fits well with forms that already have low friction and only need a defense layer for abuse.
Comparing CAPTCHA options for React apps
Different products make different tradeoffs. Here’s a practical, non-hype comparison for React form use:
| Option | Client experience | Server-side validation | Notes |
|---|---|---|---|
| reCAPTCHA | Familiar, widely recognized | Yes | Often used, but can feel more intrusive depending on configuration |
| hCaptcha | Similar pattern to reCAPTCHA | Yes | Frequently chosen as an alternative with comparable integration patterns |
| Cloudflare Turnstile | Usually lower friction | Yes | Often favored for lightweight verification flows |
| CaptchaLa | Supports 8 UI languages and Web SDKs for React | Yes | Uses first-party data only; also offers native SDKs and server SDKs |
If your app already uses React, the best choice usually comes down to operational fit: language support, backend validation workflow, how much friction you want, and whether you prefer a simple client-loader pattern or a broader SDK ecosystem. CaptchaLa also has server SDKs for PHP and Go (captchala-php, captchala-go) if your backend stack is mixed.
For teams shipping international products, the 8 UI languages can matter more than people expect. A CAPTCHA that users can understand quickly tends to reduce abandonment, especially on forms where completion already requires effort.
Implementation details that save headaches later
A few practical choices make CAPTCHA integration much easier to maintain:
- Keep the CAPTCHA token out of long-lived React state if possible. A ref or short-lived state is usually enough.
- Reset the challenge after success or after a failed submission attempt.
- Avoid duplicating validation logic in the frontend. The frontend should only manage UX; the backend should enforce trust.
- Log validation failures with enough context to monitor abuse patterns, but avoid storing unnecessary personal data.
- Test the form in slow-network conditions, since challenge scripts can delay rendering if you block on them too early.
If your backend is not Node-based, the same pattern still applies. For example, server validation can be done from PHP or Go using your backend HTTP client of choice, while the frontend stays a standard React form. That separation keeps your React code focused on experience, not security policy.
CaptchaLa also documents a server-token issue endpoint at POST https://apiv1.captcha.la/v1/server/challenge/issue, which is useful when your backend needs to initiate a challenge flow rather than relying only on a browser-rendered one. The docs are the best place to map that into your exact architecture.

When to start with a free tier and when to upgrade
If you’re protecting a small product, a contact form, or a side project, a free tier can be enough to get started. CaptchaLa’s free tier covers 1,000 validations per month, which is useful for early production traffic or internal tools.
As traffic grows, plan around actual abuse exposure rather than pageviews alone:
- 50K–200K monthly validations tends to fit many growing apps
- around 1M validations is more common for high-traffic public forms or multiple protected actions
- if you have several endpoints, count each protected submission, not just each visitor
Because the platform is designed around first-party data only, it may fit teams that want a simpler data posture when comparing providers. If you’re budgeting or deciding how to protect multiple forms, the pricing page is the quickest way to map usage to plan shape.
Adding CAPTCHA to a React form does not have to mean a clunky user experience. If you keep the challenge lightweight, validate on the server, and only ask for proof when it’s warranted, you can block a lot of automated abuse without turning your form into a maze.
Where to go next: check the docs for integration details, or review pricing if you want to estimate usage before rollout.