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)
<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)
<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>| hCaptcha | AgentGate | Notes |
|---|---|---|
class="h-captcha" | class="agentgate" | |
data-sitekey | data-sitekey | |
| none | data-action | required; configured on the site |
data-size="invisible" | data-mode="invisible" (default) | |
| normal checkbox widget | data-mode="managed" or "interactive" | |
data-callback, data-expired-callback, data-error-callback | callback, expiredCallback, errorCallback options of AgentGate.render() | |
data-chalexpired-callback, data-open-callback, data-close-callback | none | a closed check rejects with canceled |
h-captcha-response (and g-recaptcha-response) | agentgate_token | |
hl | data-lang | |
data-theme, data-size (compact) | none | system colours |
hcaptcha.render, execute, reset, remove, getResponse | AgentGate.render, execute({action}), reset, remove, getResponse | execute() 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)
curl https://api.hcaptcha.com/siteverify \
-d secret=0x… -d response="$TOKEN" -d sitekey=… -d remoteip="$IP"After (AgentGate)
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"}'| hCaptcha | AgentGate | Notes |
|---|---|---|
secret | Authorization: Bearer ags_… | or a secret JSON field |
response | token | |
sitekey | none | the secret identifies the site |
remoteip | none | |
| form-encoded body | JSON body | |
success | success | |
challenge_ts | issuedAt (Unix milliseconds) | |
hostname | origin; enforce with expectedOrigin | |
credit, score, score_reason | none | the rules decide |
error-codes | error | see below |
| hCaptcha error | AgentGate error |
|---|---|
missing-input-secret, invalid-input-secret, sitekey-secret-mismatch | invalid_credential (401) |
missing-input-response, bad-request | invalid_request (400) |
invalid-input-response | invalid_token (422) |
invalid-or-already-seen-response | token_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.