hCaptcha error codes, explained.

hCaptcha errors come from two different places, and the fix depends on which one you are looking at. Server-side, siteverify returns an error-codes array like invalid-or-already-seen-response. Client-side, the widget raises codes like rate-limited and challenge-expired. This page lists every code from both sides, what each one means, and what actually fixes it.

First: which side produced the error?

Before debugging, place the error. If the string came back in a JSON body from https://api.hcaptcha.com/siteverify, it is a server-side verification error: your backend sent something siteverify rejected. If it surfaced in the browser, in an error-callback, a rejected hcaptcha.execute() promise, or a message inside the widget, it is a client-side widget error: the widget could not load, run, or finish a challenge. The two sets do not overlap, so the code alone tells you where to look.

siteverify error codes (the error-codes array)

Your server validates a token by POSTing it to siteverify as a form-encoded body:

siteverify request
curl https://api.hcaptcha.com/siteverify \
  -d "secret=$HCAPTCHA_SECRET" \
  -d "response=P1_eyJ0eXAi...UV8w" \
  -d "remoteip=203.0.113.7"

On failure, success is false and error-codes says why:

Failed verification
{
  "success": false,
  "error-codes": ["invalid-or-already-seen-response"]
}

Here is the full list, straight from hCaptcha’s docs, with the practical fix for each:

siteverify error-codes: meaning and fix
CodeWhat it meansThe fix
missing-input-secretNo secret parameter reached siteverifySend secret as a form field in the POST body, not as a header or JSON key
invalid-input-secretThe secret is malformed or wrongCopy the secret fresh from the hCaptcha dashboard; check for a stale env var or trailing whitespace
missing-input-responseNo response parameter (the token) was sentRead h-captcha-response from the submitted form; it is empty when the user never solved the widget
invalid-input-responseThe token is invalid or malformedSend the full P1_… string untouched. Truncation, URL-decoding twice, or sending the wrong field all land here
expired-input-responseThe token was older than the validity window (120 s default)Verify immediately after the form arrives, and mint a fresh token for every attempt
already-seen-responseThe token was already verified onceTokens are single-use. Never retry siteverify with the same token; get a new one instead
invalid-or-already-seen-responseCombined form of the two rows above; what most integrations actually receiveSame fixes: one fresh token per siteverify call, used within ~120 s
bad-requestThe request itself is malformedPOST a form-encoded body (application/x-www-form-urlencoded); siteverify does not accept JSON
missing-remoteipThe remoteip parameter is missingInclude the end user’s IP, or drop the parameter entirely; it is optional
invalid-remoteipremoteip is not a valid IP addressPass one real client IP, not unknown or a comma-joined X-Forwarded-For chain
not-using-dummy-passcodeA test sitekey was paired with a real secretTest keys only work with their matching dummy secret; see hCaptcha test keys
sitekey-secret-mismatchThe sitekey is not registered to the account that owns the secretUse the sitekey and secret from the same hCaptcha site entry; this hits after dashboard reshuffles

A quirk that trips people up: hCaptcha’s current docs list expired-input-response and already-seen-response as separate codes, but live siteverify responses very often return the combined invalid-or-already-seen-response instead. Treat the three as one family. Whichever spelling you get, the token you sent is dead and you need a fresh one.

Widget and client-side errors

The widget reports errors through the error-callback you register at render time. Async hcaptcha.execute() calls can also reject with a few extra codes (challenge-closed, challenge-expired, missing-captcha, invalid-captcha-id) that never reach the callback because they describe the call, not the widget.

Registering an error-callback
<div class="h-captcha"
     data-sitekey="f5ab1c2d-7e8f-4a9b-b1c2-d3e4f5a6b7c8"
     data-error-callback="onHcaptchaError"></div>

<script>
  function onHcaptchaError(code) {
    // "rate-limited", "network-error", "challenge-error", ...
    console.warn("hCaptcha error:", code);
  }
</script>
Client-side widget and execute() error codes
CodeWhen it firesWhat to do
rate-limitedToo many requests from this client; often paired with HTTP 429s to hCaptcha endpointsBack off and slow down. If it persists on first load, the IP itself is the problem (see below)
network-errorThe widget cannot reach hCaptcha (offline, blocked, or filtered)Check connectivity and anything intercepting requests to hcaptcha.com: ad blockers, proxies, CSP
invalid-dataAn hCaptcha endpoint rejected the data the widget sentUsually bad render parameters; check the sitekey and any rqdata you pass in
challenge-errorThe challenge failed during setupTransient on hCaptcha’s side more often than yours; reset the widget and retry once
challenge-closedThe user dismissed the open challenge (async execute() rejection only)Treat as a cancel, not a failure; re-arm the widget for the next attempt
challenge-expiredThe challenge sat open past its time limit (async execute() rejection only)Common in automation that opens a challenge and stalls; answer or close promptly
missing-captchaYou called execute() but no widget exists (async only)Render before executing; watch for SPA re-renders that unmount the widget container
invalid-captcha-idThe widget ID passed to the API call does not exist (async only)Store the ID returned by hcaptcha.render() and reuse exactly that one
internal-errorThe hCaptcha client hit an internal errorReset and retry; if it repeats across browsers, check the hCaptcha status page
script-errorThe hCaptcha JS SDK itself failed to loadThe api.js request was blocked or failed; check CSP, ad blockers, and the script tag URL

rate-limited and 429s: usually the IP, not the code

rate-limited is the widget error people search most, because the message it produces (“Rate limited or network error, please retry”) shows up even when your integration is correct. Under the hood the widget’s requests to hCaptcha are coming back as HTTP 429.

If it appears after a burst of activity, it is a genuine rate limit: a retry loop hammering execute(), a component re-rendering the widget on every state change, or a script solving in a tight loop. Add backoff and the error goes away.

If a fresh session hits rate-limited on the first widget load, stop debugging your code. hCaptcha scores the client before any challenge renders, and addresses it distrusts (datacenter ranges, overused residential proxies, VPN exits) get throttled immediately. The fix is a cleaner IP. No client-side change makes a burned address trustworthy again.

The two errors that eat automation pipelines

If you solve hCaptcha programmatically, two failure modes account for most of the siteverify rejections you will ever see. They come from how tokens themselves behave, which is covered in depth in how hCaptcha tokens work.

invalid-or-already-seen-response: tokens are single-use

Every P1_ token is consumed by its first siteverify call. The second call with the same string fails, every time, by design. That single rule explains a whole class of confusing bugs:

Retry loops that reuse the token. A submit fails for an unrelated reason (timeout, 500, validation), your code retries the POST, and the captcha field still holds the old token. First attempt consumed it; every retry now fails verification. The retry must include minting a new token, not just resending the form.

Double verification. Some stacks verify in middleware and then again in the handler, or a frontend “pre-validates” the token before the real submit. The first check passes and silently kills the token; the second gets invalid-or-already-seen-response. Verify exactly once, server-side.

Sharing one token across workers. Caching a solved token and handing it to multiple jobs cannot work. One job wins the race; the rest fail. Each submit needs its own token.

expired-input-response: the ~120-second window

A token is valid for roughly 120 seconds from issue. The clock starts when the token is minted, not when you read it, so every second spent between solve and verification counts against the budget. Pipelines that solve early and submit late are the classic case: solve the captcha, scrape three more pages, fill the form, submit, and the token is dead on arrival.

The reliable pattern is to make token acquisition the last step before submit. Prepare everything else first, mint the token, inject it into h-captcha-response, and POST within seconds. If a flow legitimately takes longer than the window, mint the token after the slow part, never before.

Getting a fresh token per submit

Both failure modes reduce to the same requirement: one fresh, unused token per submit attempt, used immediately. That is exactly the contract of NoneCap’s solve API. Each POST /v1/solves returns a newly minted P1_ token for your sitekey and page, never a cached or reused one, so already-seen rejections cannot happen on the first verification:

Mint a fresh token
# One fresh token per submit attempt:
curl "https://api.nonecap.com/v1/solves?wait=90" \
  -H "Authorization: Bearer $NONECAP_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type":    "hcaptcha",
    "sitekey": "f5ab1c2d-7e8f-4a9b-b1c2-d3e4f5a6b7c8",
    "url":     "https://target.example/login"
  }'
Response
{
  "id":              "solve_01HQF7K3JKWZX",
  "object":          "solve",
  "type":            "hcaptcha",
  "status":          "solved",
  "token":           "P1_eyJ0eXAi...UV8w",
  "credits_charged": 1
}

Call it at submit time, drop token into h-captcha-response, and verify once. Solves that fail or expire are auto-refunded, so an error on hCaptcha’s side never costs a credit. Regular, invisible, and enterprise (rqdata) sitekeys are all supported; for enterprise specifics see enterprise rqdata.

Debugging checklist

When an hCaptcha integration fails and the error is not obvious, this order finds it fastest:

1. Log the raw siteverify response. Many wrappers swallow error-codes and report a bare “verification failed”. The array names the exact problem.

2. Check the token’s age and history. Log when each token was minted and how many times you verified it. Anything verified twice or after ~120 seconds explains invalid-or-already-seen-response with no further digging.

3. Confirm key pairing. sitekey-secret-mismatch and not-using-dummy-passcode both mean your keys do not belong together. They typically appear after switching between test and production keys.

4. Watch the network panel for 429s. Client-side weirdness with no callback error often turns out to be rate-limited in disguise.

Last updated June 2026.

Frequently asked

What does invalid-or-already-seen-response mean?
The token you sent to siteverify was either already verified once or is no longer valid. hCaptcha tokens are strictly single-use: the first siteverify call consumes the token, and every later call with the same string fails. Retry loops that resend the same token, double form submits, and webhooks that re-verify are the usual culprits. The fix is one fresh token per verification, used within about 120 seconds. See how hCaptcha tokens work.
Why does the hCaptcha widget say "rate limited" (429)?
The widget shows it after rate-limited errors, which come with HTTP 429s from hCaptcha endpoints. It means this client has sent too many requests: rapid solve attempts, a render loop, or simply an IP that hCaptcha already distrusts. Datacenter ranges and overused proxies can hit it on the very first load. Back off, fix any loop, and if a clean session still gets it immediately, change the IP, not the code.
How long is an hCaptcha token valid?
About 120 seconds by default, and exactly one siteverify call. Verify late and you get expired-input-response; verify twice and you get already-seen-response (or the combined invalid-or-already-seen-response). Budget your flow so the token is minted, injected, and verified inside that window.
Does NoneCap charge for failed solves?
No. Billing starts at one credit per hCaptcha challenge round, charged on success only; failed, cancelled, and expired solves are auto-refunded. Credits run $0.25 to $0.50 per 1,000 depending on pack size, and new accounts get 100 free credits. See pricing.

Start solving hCaptcha in minutes.

100 free credits on signup. Pay per solve, credits never expire, failed solves auto-refunded.