hCaptcha was deliberately designed to be easy to adopt for sites already running reCAPTCHA v2 — its widget, attribute names, and verification flow intentionally mirror the shape of reCAPTCHA so that a migration is a rename rather than a rewrite. That similarity is a gift and a trap. The gift is that you can move a basic integration in an afternoon. The trap is that the similarity tempts teams to assume the pieces are interchangeable, leave one Google reference behind, and ship a form that looks migrated but verifies against the wrong service. This guide walks the swap one layer at a time so nothing is left half-converted.
Step 1: Register the Site and Get hCaptcha Keys
Create an hCaptcha account and add your site to get a site key (public, used in the browser) and a secret key (private, used only on your server). This is the same two-key model reCAPTCHA uses, so the place those values live in your code does not change — only the values do. Keep the secret out of client-side code and out of your repository, exactly as you did for the reCAPTCHA secret. If your old keys were committed anywhere, rotate the mental habit too: treat the migration as the moment to move both secrets into environment variables if they were not already there.
Step 2: Swap the Client Script and Widget
Replace the reCAPTCHA script include with hCaptcha's, and change the widget container's class and data attribute. The before-and-after for a standard checkbox widget is small:
<!-- Before (reCAPTCHA v2) --> <script src="https://www.google.com/recaptcha/api.js" async defer></script> <div class="g-recaptcha" data-sitekey="RECAPTCHA_SITE_KEY"></div> <!-- After (hCaptcha) --> <script src="https://js.hcaptcha.com/1/api.js" async defer></script> <div class="h-captcha" data-sitekey="HCAPTCHA_SITE_KEY"></div>
That is the heart of the front-end change:
g-recaptcha becomes h-captcha,
the script host changes, and the site key changes. If you
used explicit rendering via JavaScript rather than the
auto-rendered class, hCaptcha exposes an
hcaptcha.render(),
hcaptcha.getResponse(), and
hcaptcha.reset() API that parallels
reCAPTCHA's grecaptcha equivalents — the
same multi-widget discipline applies, so if you render
several on a page, the
explicit-render and widget-ID rules we covered for
reCAPTCHA
carry over conceptually.
Step 3: Read the Renamed Response Field
On submit, the token your form posts changes name. Where
reCAPTCHA put its token in
g-recaptcha-response, hCaptcha puts it in
h-captcha-response. Every place your backend
reads the old field name must be updated, or the server
will look for a token that no longer exists and treat
every legitimate submission as missing one:
// Before $token = $_POST['g-recaptcha-response']; // After $token = $_POST['h-captcha-response'];
This single rename is the most common migration miss. The
widget renders, the user solves it, the front end looks
perfect — and the backend silently rejects everyone
because it is still reading g-recaptcha-response.
It mirrors the broader class of
token handoff failures
where the client and server disagree about the field
name.
Step 4: Change the Server-Side Verification Endpoint
This is the step people forget exists. reCAPTCHA verifies
tokens against
https://www.google.com/recaptcha/api/siteverify;
hCaptcha verifies against
https://api.hcaptcha.com/siteverify. The
request shape is the same — a POST with
secret and response parameters
(and an optional remoteip) — and the JSON
response again carries a top-level success
boolean. Update the URL and the secret, keep the
success-check logic, and re-test:
// hCaptcha server-side verification (PHP sketch)
$verify = file_get_contents(
'https://api.hcaptcha.com/siteverify',
false,
stream_context_create(['http' => [
'method' => 'POST',
'header' => 'Content-Type: application/x-www-form-urlencoded',
'content' => http_build_query([
'secret' => getenv('HCAPTCHA_SECRET'),
'response' => $token,
'remoteip' => $_SERVER['REMOTE_ADDR'] ?? null,
]),
]])
);
$result = json_decode($verify, true);
if (empty($result['success'])) {
// reject: do not process the form
}
Because the success field is named the same, a sloppy migration that updates the field name but leaves the old Google verification URL in place will fail closed (Google rejects an hCaptcha token), which at least breaks loudly. The dangerous inverse is leaving verification disabled "temporarily" during the swap and forgetting to turn it back on — a form that renders a CAPTCHA but never checks the token is open to every bot that ignores the widget entirely.
Step 5: Test the Failure Paths, Not Just the Happy Path
-
Solve and submit. Confirm a real
solve produces a token and the server returns
success: trueand processes the form. - Submit with no token. Strip the field with dev tools and confirm the server rejects it. If it accepts an empty token, your verification is not actually wired up.
- Submit a stale or reused token. hCaptcha tokens, like reCAPTCHA's, are single-use and time-limited; confirm a replayed token is rejected, the same lifecycle discipline behind timeout-or-duplicate failures.
-
Remove every Google reference. Grep
the codebase for
g-recaptcha,grecaptcha,recaptcha/api.js, and the old siteverify URL. Any survivor is a half-migrated path.
Where rCAPTCHA Fits in a Migration Decision
Switching providers is a good moment to ask the larger question: do you want a visible image-puzzle widget at all? Both reCAPTCHA v2 and hCaptcha's interactive mode still put friction in front of real users, and motivated bots increasingly solve image challenges anyway. Behavioral verification like rCAPTCHA aims to reduce that visible friction by leaning on signals rather than puzzles, which is a different trade-off than a like-for-like provider swap. We are not pretending any single layer eliminates every bot, abuse, or accessibility problem — none does — but if you are already opening the integration to migrate, it is worth weighing a model change against a vendor change. Our CAPTCHA vs hCaptcha vs reCAPTCHA comparison lays the options side by side.
People Also Ask: reCAPTCHA to hCaptcha
Is hCaptcha a drop-in replacement for reCAPTCHA?
For a basic v2-style integration it is close: the widget class, script host, response-field name, and verification URL change, but the overall flow and two-key model are the same. Advanced or heavily customized reCAPTCHA setups need more care.
What replaces g-recaptcha-response in hCaptcha?
hCaptcha posts the token in
h-captcha-response. Update every server-side
read of g-recaptcha-response to the new
field name.
What verification endpoint does hCaptcha use?
https://api.hcaptcha.com/siteverify, with
secret and response POST
parameters and a top-level success boolean
in the JSON response — the same request shape as Google's
siteverify, just a different host.
Do I have to remove all the Google reCAPTCHA code?
Yes. Leftover grecaptcha calls, the old
script tag, or the old siteverify URL produce
half-migrated paths that either fail loudly or, worse,
skip verification. Grep the codebase and remove every
reference.
Conclusion
Migrating from reCAPTCHA to hCaptcha is four renames and
one new endpoint: new keys, the
h-captcha widget class and script host, the
h-captcha-response field, and the
api.hcaptcha.com/siteverify URL. The work
that actually protects you is the testing — submit with
no token, submit a reused token, and grep out every
surviving Google reference so no path quietly skips
verification during the cutover. Do those, and the switch
is uneventful. Skip them, and you ship a form that looks
protected and isn't.