Migrate from hCaptcha

Replace an hCaptcha widget and its siteverify call with AgentGate, attribute by attribute and field by field.

hCaptcha's widget and server check map almost one to one onto AgentGate's. Visitors stop solving image puzzles: most see nothing, and the rest get a short press-and-hold check with a no-pointer alternative.

Note

hCaptcha names below are from hCaptcha's public documentation. Check them against the version you use.

1. Create a site

Add your site in the console with every origin that serves the form, and one action per form. You get a site key (site_…) and a backend secret (ags_…).

2. Swap the widget

Before (hCaptcha)

HTML
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
<form method="post" action="/signup">
  <div class="h-captcha" data-sitekey="10000000-ffff-ffff-ffff-000000000001" data-callback="onToken"></div>
  <button>Sign up</button>
</form>

After (AgentGate)

HTML
<script src="https://agentgate.example/sdk/v1/agentgate.js" async></script>
<form method="post" action="/signup">
  <div class="agentgate" data-sitekey="site_…" data-action="signup" data-mode="managed"></div>
  <button>Sign up</button>
</form>
hCaptchaAgentGateNotes
class="h-captcha"class="agentgate"
data-sitekeydata-sitekey
nonedata-actionrequired; configured on the site
data-size="invisible"data-mode="invisible" (default)
normal checkbox widgetdata-mode="managed" or "interactive"
data-callback, data-expired-callback, data-error-callbackcallback, expiredCallback, errorCallback options of AgentGate.render()
data-chalexpired-callback, data-open-callback, data-close-callbacknonea closed check rejects with canceled
h-captcha-response (and g-recaptcha-response)agentgate_token
hldata-lang
data-theme, data-size (compact)nonesystem colours
hcaptcha.render, execute, reset, remove, getResponseAgentGate.render, execute({action}), reset, remove, getResponseexecute() resolves with the token

In your CSP, replace the hCaptcha hosts (including frame-src) with your AgentGate endpoint in script-src and connect-src, plus worker-src blob:.

3. Swap the server call

Before (hCaptcha)

Shell
curl https://api.hcaptcha.com/siteverify \
  -d secret=0x… -d response="$TOKEN" -d sitekey=… -d remoteip="$IP"

After (AgentGate)

Shell
curl https://agentgate.example/v1/siteverify \
  -H "Authorization: Bearer $AGENTGATE_SECRET" -H "Content-Type: application/json" \
  -d '{"token":"'"$TOKEN"'","expectedAction":"signup","expectedOrigin":"https://app.example.com"}'
hCaptchaAgentGateNotes
secretAuthorization: Bearer ags_…or a secret JSON field
responsetoken
sitekeynonethe secret identifies the site
remoteipnone
form-encoded bodyJSON body
successsuccess
challenge_tsissuedAt (Unix milliseconds)
hostnameorigin; enforce with expectedOrigin
credit, score, score_reasonnonethe rules decide
error-codeserrorsee below
hCaptcha errorAgentGate error
missing-input-secret, invalid-input-secret, sitekey-secret-mismatchinvalid_credential (401)
missing-input-response, bad-requestinvalid_request (400)
invalid-input-responseinvalid_token (422)
invalid-or-already-seen-responsetoken_used (409) or expired_token (422)

4. Differences to plan for

  • Action required. Every token is bound to an action; pass the same one as expectedAction.
  • Token lifetime. Two minutes by default; the widget verifies at submit time.
  • Monitor mode first. New sites pass every valid token and report wouldDecision; switch to enforce when ready.
  • Test keys. hCaptcha's test site key and secret map to AgentGate's testing keys.

View as Markdown