Site Overlay

ProCaptcha in React: The Complete Setup, Integration & Customization Guide






ProCaptcha in React: Setup, Integration & Customization Guide







Developer Guide · React · Bot Protection · Privacy · ~12 min read

ProCaptcha in React: The Complete Setup, Integration & Customization Guide

If you’ve been using reCAPTCHA and quietly ignoring the part where Google harvests behavioral data from every user who clicks “I’m not a robot” — this article is for you. ProCaptcha by Prosopo is a privacy-preserving, decentralized CAPTCHA library built specifically for developers who want real bot protection without trading their users’ data for it. And yes, it has a proper React CAPTCHA library — so integration doesn’t mean writing arcane workarounds.

This guide covers everything from initial procaptcha installation to advanced procaptcha customization, server-side token verification, and a few gotchas that the official docs gloss over. Whether you’re getting started with procaptcha for the first time or migrating away from a tracking-heavy alternative, you’ll find working code and real context here.

What Is ProCaptcha and Why Should React Developers Care?

ProCaptcha is an open-source, decentralized CAPTCHA solution developed by Prosopo. Instead of routing verification through a centralized server owned by a large tech company, it uses a distributed network of validators. This means no single authority collects user interaction data, no cookies are silently dropped on your users, and compliance with GDPR and similar regulations becomes significantly less of a headache.

For React developers specifically, the @prosopo/procaptcha-react package provides a drop-in component that integrates cleanly into the React component model. You pass a site key and a callback — and that’s genuinely most of the setup. The widget handles its own state, renders its own UI, and calls your callback with a verification token when a human successfully completes the challenge. It’s designed to feel like something that belongs in a modern React app, not a bolted-on script tag from 2012.

The deeper reason React developers should pay attention: privacy-first architecture is becoming a competitive differentiator, not just an ethical nicety. Users are more aware of tracking than they were five years ago, regulations are tightening globally, and “powered by reCAPTCHA” increasingly raises eyebrows in privacy-conscious communities. Switching to a React privacy CAPTCHA like ProCaptcha signals something meaningful about how you build software — and it doesn’t cost you anything in terms of developer experience.

ProCaptcha Installation: Setting Up Your React Project

Before writing a single line of component code, you need two things: an npm package and a site key. The procaptcha installation process starts at the Prosopo dashboard, where you register your application and receive a public site key. Keep your secret key server-side — exposing it in client code is the kind of mistake that makes for embarrassing post-mortems.

Install the React-specific package using npm or yarn:

# Using npm
npm install @prosopo/procaptcha-react

# Using yarn
yarn add @prosopo/procaptcha-react

The package pulls in its own dependencies, including the core @prosopo/procaptcha engine, so you don’t need to install anything else for the client side. Once installed, store your site key in an environment variable — REACT_APP_PROSOPO_SITE_KEY in a Create React App setup, or VITE_PROSOPO_SITE_KEY if you’re on Vite. Hardcoding keys in source files is a fast path to rotating credentials at 2am on a Friday.

At this stage, your project structure doesn’t need any special configuration. ProCaptcha doesn’t require a custom webpack plugin, doesn’t fight with TypeScript strict mode, and works with both class and functional components — though functional components with hooks are the natural home for it. If you’re using Next.js, there are a couple of additional considerations around SSR that we’ll address when we get to the implementation section.

Core Integration: Adding the ProCaptcha Component to React

The fundamental procaptcha setup in a React functional component is straightforward. You import the Procaptcha component, construct a config object with your site key and environment, and wire up the onHuman callback to capture the verification token. Here’s a minimal but complete working example:

import React, { useState } from 'react';
import { Procaptcha } from '@prosopo/procaptcha-react';

const ContactForm = () => {
  const [captchaToken, setCaptchaToken] = useState(null);
  const [isVerified, setIsVerified] = useState(false);

  const captchaConfig = {
    account: {
      environment: 'production', // or 'development' for local testing
      siteKey: process.env.REACT_APP_PROSOPO_SITE_KEY,
    },
  };

  const handleHumanVerification = (output) => {
    // output contains the verification token
    setCaptchaToken(output.processCaptchaOutput);
    setIsVerified(true);
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!captchaToken) {
      alert('Please complete the CAPTCHA verification.');
      return;
    }
    // Send form data + captchaToken to your backend
    await submitFormWithToken({ captchaToken });
  };

  return (
    <form onSubmit={handleSubmit}>
      {/* your form fields */}
      <Procaptcha
        config={captchaConfig}
        callbacks={{ onHuman: handleHumanVerification }}
      />
      <button type="submit" disabled={!isVerified}>
        Submit
      </button>
    </form>
  );
};

export default ContactForm;

A few things worth noting here. The onHuman callback fires only when the user successfully passes verification — not on render, not on focus, not when the widget loads. This makes it trivially easy to gate form submissions: check whether your token state is populated before allowing the submit to proceed. The disabled prop on the button does this visually, but always enforce the same check in your submit handler because users have browser dev tools.

The environment field in the config accepts 'production' or 'development'. In development mode, ProCaptcha connects to Prosopo’s test network, which means you get the full verification flow without touching production resources. This is genuinely useful for local testing — you don’t have to fake the CAPTCHA interaction or mock the component to run your form tests. Set up a .env.local file and switch environments without changing any component code.

For Next.js projects, the Procaptcha component must be rendered client-side only. Wrap it in a dynamic import with ssr: false, or use the 'use client' directive in the App Router. Attempting to server-render the widget will throw errors because it depends on browser APIs that don’t exist in a Node environment. This isn’t a ProCaptcha quirk — it applies to any CAPTCHA library that manipulates the DOM directly.

Server-Side Token Verification: Closing the Security Loop

Rendering the widget and capturing the token is only half the job. A procaptcha verification flow isn’t complete until your backend independently confirms that the token is valid. Any CAPTCHA implementation that trusts the client to self-report its verification status is security theater — a determined bot author can skip the widget entirely and just set the token field to whatever value your frontend expects.

When the onHuman callback fires, include the token in your form submission payload and send it to your server. On the server, make a POST request to the Prosopo verification endpoint with your secret key and the received token:

// Node.js / Express backend example
const axios = require('axios');

app.post('/submit-form', async (req, res) => {
  const { captchaToken, ...formData } = req.body;

  try {
    const verificationResponse = await axios.post(
      'https://api.prosopo.io/siteverify',
      {
        secret: process.env.PROSOPO_SECRET_KEY,
        token: captchaToken,
      }
    );

    if (!verificationResponse.data.verified) {
      return res.status(400).json({ error: 'CAPTCHA verification failed.' });
    }

    // Token is valid — process the form submission
    await processFormData(formData);
    return res.status(200).json({ success: true });

  } catch (error) {
    console.error('ProCaptcha verification error:', error);
    return res.status(500).json({ error: 'Verification service unavailable.' });
  }
});

The verification response includes a verified boolean and additional metadata. Treat a missing or malformed response as a failure — don’t default to trusting the token if the verification service returns an unexpected structure. This matters in edge cases like network timeouts: failing closed (rejecting the submission) is safer than failing open.

Tokens are single-use and time-limited. If a user completes the CAPTCHA, navigates away, and comes back to submit the same form twenty minutes later, the token will likely be expired. Handle this gracefully in your UX — either reset the CAPTCHA widget on submission failure with a user-friendly message, or detect token expiry proactively and prompt re-verification before the user reaches the submit step. The procaptcha verification callback system gives you enough hooks to implement either approach.

ProCaptcha Customization: Themes, Callbacks, and Advanced Config

Out of the box, the ProCaptcha widget renders with a default theme that’s clean but deliberately neutral. ProCaptcha customization options let you adjust the visual presentation to match your application’s design system without forking the library or wrestling with CSS specificity wars. The config object accepts a theme property that controls color mode, and you can apply additional styling through standard CSS targeting the widget’s container element.

Beyond visual customization, the callbacks prop accepts multiple handler functions beyond onHuman. You can listen for onChallengeExpired to reset your form state when a token goes stale, onError to handle network or validation failures gracefully, and onClose if the challenge widget can be dismissed. Using these callbacks properly is what separates a production-quality React bot protection implementation from a demo that happens to work on a good day:

const callbacks = {
  onHuman: (output) => {
    setCaptchaToken(output.processCaptchaOutput);
    setIsVerified(true);
  },
  onChallengeExpired: () => {
    setCaptchaToken(null);
    setIsVerified(false);
    // Optionally notify the user to re-verify
  },
  onError: (error) => {
    console.error('ProCaptcha error:', error);
    // Show fallback UI or retry mechanism
  },
};

For teams building accessible applications, it’s worth noting that ProCaptcha’s challenge mechanism is designed to be usable without relying solely on image recognition — the traditional accessibility weak point of visual CAPTCHAs. The decentralized verification model enables alternative challenge types that don’t punish users with visual impairments. This is a meaningful architectural difference from older CAPTCHA systems, and it’s something worth communicating to stakeholders when justifying the switch from a mainstream alternative.

If your application has multiple forms — a login form, a contact form, a newsletter signup — you don’t need separate site keys for each. One site key covers your entire registered domain. You can reuse the same config object across multiple component instances, or create a small custom hook that encapsulates the CAPTCHA state logic and returns captchaToken, isVerified, and the config object. This keeps your form components clean and makes the React CAPTCHA library integration easy to audit and maintain.

ProCaptcha vs. reCAPTCHA: The Practical Comparison

Let’s be concrete about what changes when you switch from reCAPTCHA v2 or v3 to React ProCaptcha. The integration complexity is roughly equivalent — both require a site key, a frontend component, and server-side token verification. The API surface of ProCaptcha is arguably simpler: there’s no grecaptcha.execute() to manage, no invisible v3 score thresholds to tune, and no action strings to keep synchronized between frontend and backend.

The substantive difference is in what happens to your users’ data during the verification process. reCAPTCHA v3 operates by analyzing user behavior across all sites that use it, building behavioral profiles that inform its risk scoring. This is effective at catching bots — it’s also a surveillance mechanism that operates without explicit user consent in most implementations. ProCaptcha’s decentralized CAPTCHA architecture means verification happens through a distributed validator network with no central data store accumulating cross-site behavioral profiles. Your users are verified, not catalogued.

From a purely pragmatic standpoint: if your application serves users in the EU, you’re currently in a legally ambiguous position using reCAPTCHA without specific consent mechanisms for the data it collects. Several European data protection authorities have issued guidance suggesting that simply using Google services without user consent may violate GDPR. ProCaptcha sidesteps this entirely. That’s not a theoretical compliance win — it’s a concrete risk reduction with real legal and reputational stakes attached.

Complete ProCaptcha Example: A Production-Ready React Form

The following procaptcha example pulls together everything covered above into a form component that’s genuinely ready for production use — not a stripped-down demo missing the error handling and edge cases that bite you in staging. It includes environment-aware configuration, full callback coverage, accessible UI state management, and a clear submission flow:

import React, { useState, useCallback } from 'react';
import { Procaptcha } from '@prosopo/procaptcha-react';

const CAPTCHA_CONFIG = {
  account: {
    environment: process.env.NODE_ENV === 'production'
      ? 'production'
      : 'development',
    siteKey: process.env.REACT_APP_PROSOPO_SITE_KEY,
  },
};

const ProductionForm = () => {
  const [formState, setFormState] = useState({
    name: '',
    email: '',
    message: '',
  });
  const [captchaToken, setCaptchaToken] = useState(null);
  const [status, setStatus] = useState('idle'); // idle | submitting | success | error
  const [captchaError, setCaptchaError] = useState(null);

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setFormState(prev => ({ ...prev, [name]: value }));
  };

  const captchaCallbacks = useCallback(() => ({
    onHuman: (output) => {
      setCaptchaToken(output.processCaptchaOutput);
      setCaptchaError(null);
    },
    onChallengeExpired: () => {
      setCaptchaToken(null);
      setCaptchaError('Verification expired. Please complete the CAPTCHA again.');
    },
    onError: (err) => {
      setCaptchaToken(null);
      setCaptchaError('CAPTCHA verification failed. Please try again.');
      console.error('ProCaptcha error:', err);
    },
  }), []);

  const handleSubmit = async (e) => {
    e.preventDefault();

    if (!captchaToken) {
      setCaptchaError('Please complete the CAPTCHA verification before submitting.');
      return;
    }

    setStatus('submitting');

    try {
      const response = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ ...formState, captchaToken }),
      });

      if (!response.ok) throw new Error('Submission failed');

      setStatus('success');
    } catch (err) {
      setStatus('error');
      console.error('Form submission error:', err);
    }
  };

  if (status === 'success') {
    return <p role="status">Message sent successfully. We'll be in touch.</p>;
  }

  return (
    <form onSubmit={handleSubmit} noValidate>
      <div>
        <label htmlFor="name">Name</label>
        <input
          id="name"
          name="name"
          type="text"
          value={formState.name}
          onChange={handleInputChange}
          required
          autoComplete="name"
        />
      </div>

      <div>
        <label htmlFor="email">Email</label>
        <input
          id="email"
          name="email"
          type="email"
          value={formState.email}
          onChange={handleInputChange}
          required
          autoComplete="email"
        />
      </div>

      <div>
        <label htmlFor="message">Message</label>
        <textarea
          id="message"
          name="message"
          value={formState.message}
          onChange={handleInputChange}
          required
          rows={5}
        />
      </div>

      <Procaptcha
        config={CAPTCHA_CONFIG}
        callbacks={captchaCallbacks()}
      />

      {captchaError && (
        <p role="alert" style={{ color: 'red' }}>{captchaError}</p>
      )}

      {status === 'error' && (
        <p role="alert" style={{ color: 'red' }}>
          Something went wrong. Please try again.
        </p>
      )}

      <button
        type="submit"
        disabled={status === 'submitting' || !captchaToken}
        aria-busy={status === 'submitting'}
      >
        {status === 'submitting' ? 'Sending...' : 'Send Message'}
      </button>
    </form>
  );
};

export default ProductionForm;

This component uses useCallback to stabilize the callbacks object reference — passing a new object on every render would cause the Procaptcha widget to remount unnecessarily, which resets the verification state and frustrates users mid-completion. Small details like this are the difference between “it works in the demo” and “it works in production with real users on slow connections.”

The status state machine approach (idle → submitting → success/error) is a clean pattern for managing async form submissions. It prevents double-submissions, gives you clear branches for rendering different UI states, and makes the component logic readable without a state management library. Pair it with the procaptcha verification token flow and you have a form that handles the full lifecycle — including the failure modes — explicitly.

Note the role="alert" and role="status" attributes on feedback elements. Screen readers announce these regions automatically when their content changes, which means your accessibility story doesn’t require additional work beyond applying the right ARIA roles. This is the kind of detail that matters for WCAG compliance and that most quick-start tutorials skip entirely in the interest of keeping code samples short.

Getting Started Checklist: From Zero to Verified in Under 30 Minutes

If you’re getting started with procaptcha fresh and want a clear sequence to follow, here’s the complete path from nothing to a working, verified implementation. Each step is genuinely necessary — this isn’t padded with optional diversions:

  • Register at Prosopo: Create an account, register your application domain, and obtain your public site key and private secret key.
  • Install the package: Run npm install @prosopo/procaptcha-react in your React project root.
  • Configure environment variables: Add REACT_APP_PROSOPO_SITE_KEY (public) to your frontend env, and PROSOPO_SECRET_KEY (private) to your backend env only.
  • Add the component: Import Procaptcha, build the config object, wire up onHuman to capture the token.
  • Gate form submission: Disable submit until captchaToken is populated; include the token in your form payload.
  • Verify server-side: POST the token to Prosopo’s verification API with your secret key before processing any submission.
  • Test in development mode: Set environment: 'development' and run through the full flow locally before switching to production.

The full round-trip — widget loads, user completes challenge, token captured, form submitted, token verified server-side — typically adds less than 500ms to the critical path in normal network conditions. That’s a negligible UX cost for eliminating automated form submissions entirely.

For teams already using TypeScript, the @prosopo/procaptcha-react package ships with type definitions. The config object, callback signatures, and output types are fully typed, which means your IDE will catch misconfigurations at development time rather than at runtime in staging. The ProcaptchaOutput type exported by the package defines exactly what your onHuman callback receives, so you don’t need to guess at the shape of the output object.

FAQ

How do I add ProCaptcha to a React application?

Install @prosopo/procaptcha-react via npm or yarn. Import the Procaptcha component into your form component, pass a config object containing your site key and target environment, and provide an onHuman callback that stores the returned verification token. Include that token in your form submission payload and verify it server-side by calling the Prosopo verification API with your secret key. The entire client-side setup typically takes less than 20 lines of code.

Is ProCaptcha really privacy-preserving compared to reCAPTCHA?

Yes, in a meaningful and architectural sense. reCAPTCHA v3 works by building behavioral profiles across all participating sites — your users’ interaction patterns are continuously analyzed by Google’s systems. ProCaptcha uses a decentralized validator network: no single entity accumulates cross-site user data, no cookies tied to third-party tracking are set, and no personally identifiable information is collected during the verification process. This distinction matters practically for GDPR compliance and for users in privacy-conscious environments.

How do I verify ProCaptcha tokens on the server side?

When the user passes the CAPTCHA challenge, your onHuman callback receives an output object containing the verification token. Pass this token to your backend with the form submission. On the server, make a POST request to https://api.prosopo.io/siteverify with a JSON body containing your secret key and the received token. The API returns a response with a verified boolean — only proceed with processing the submission if this value is true. Always fail closed on network errors or unexpected response shapes.

Semantic Core Reference:
procaptcha · React ProCaptcha · procaptcha installation · procaptcha setup · procaptcha getting started · procaptcha tutorial · procaptcha example · procaptcha verification · procaptcha customization · React CAPTCHA library · React bot protection · React privacy CAPTCHA · React privacy-preserving CAPTCHA · React decentralized CAPTCHA · React Prosopo CAPTCHA · Prosopo procaptcha npm · decentralized CAPTCHA React · privacy-first CAPTCHA · CAPTCHA without tracking · Web3 CAPTCHA React