knadh/listmonk · warning
hCaptcha failed: %s
Error message
hCaptcha failed: %s
What it means
verifyHCaptcha returns this when the hCaptcha siteverify API responds with success=false. The response's error-codes array is joined into the message, explaining why hCaptcha rejected the token (bad secret, expired/already-used token, missing-input, etc.). This is the normal rejection path for failed human verification, not an infrastructure error.
Source
Thrown at internal/captcha/captcha.go:180
"response": {token},
})
if err != nil {
return err, false
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return err, false
}
var r hCaptchaResp
if err := json.Unmarshal(body, &r); err != nil {
return err, true
}
if !r.Success {
return fmt.Errorf("hCaptcha failed: %s", strings.Join(r.ErrorCodes, ",")), false
}
return nil, true
}
// verifyAltcha verifies an Altcha response.
func (c *Captcha) verifyAltcha(payload string) (error, bool) {
valid, err := altcha.VerifySolution(payload, c.altcha.HMACKey, true)
if err != nil {
return fmt.Errorf("failed to verify captcha solution: %w", err), false
}
if !valid {
return fmt.Errorf("captcha verification failed"), false
}
// Disallow token reuse.
if _, err := tmptokens.Check(payload); err == nil {View on GitHub (pinned to 670c01717d)
Solutions
- Read the joined error-codes in the message: 'invalid-input-secret' means fix the secret key; 'timeout-or-duplicate' means the token expired or was reused.
- For timeout-or-duplicate, ensure the token is submitted once — reset the hCaptcha widget and fetch a fresh token after each attempt.
- Verify the secret key matches the sitekey's environment (hCaptcha dashboard).
- Return a user-facing 'captcha failed, please retry' message and never treat this as a 5xx server error.
- Confirm the form actually posts the h-captcha-response field as the token.
Example fix
// before: generic rejection message
return fmt.Errorf("hCaptcha failed: %s", strings.Join(r.ErrorCodes, ",")), false
// after: map common codes to actionable messages
switch {
case contains(r.ErrorCodes, "invalid-input-secret"):
return errors.New("hcaptcha secret key is invalid"), false
case contains(r.ErrorCodes, "timeout-or-duplicate"):
return errors.New("captcha token expired or reused, retry"), false
} Defensive patterns
Strategy: try-catch
Validate before calling
token := r.PostFormValue("h-captcha-response")
if token == "" {
return errors.New("hCaptcha token missing from form submission")
}
// optionally check token shape before calling siteverify
if len(token) < 20 {
return errors.New("hCaptcha token malformed")
} Type guard
func hCaptchaTokenPresent(r *http.Request) bool {
return r.PostFormValue("h-captcha-response") != ""
} Try / catch
if err, ok := captcha.Verify(token); !ok {
if err != nil && strings.HasPrefix(err.Error(), "hCaptcha failed:") {
switch {
case strings.Contains(err.Error(), "timeout-or-duplicate"):
// user retry: reset widget, new token
case strings.Contains(err.Error(), "invalid-input-secret"):
log.Error("wrong hCaptcha secret key — fix config")
default:
// likely a bot: reject normally
}
http.Error(w, "captcha failed, please retry", http.StatusBadRequest)
return
}
} Prevention
- Reset the hCaptcha widget after every failed submission so a fresh token is generated
- Use distinct sitekey/secret pairs per environment and verify them in deploy checks
- Expect timeout-or-duplicate on double submits — handle it as a normal user retry
- Never retry automatically with the same token; siteverify accepts each token once
When it happens
Trigger: Calling Verify with an hCaptcha token when hCaptcha's siteverify endpoint replies {"success": false, "error-codes": [...]}, e.g. the user failed the challenge, the token was already redeemed, the response token expired (~2 minutes), or the secret key is wrong.
Common situations: User submits form twice so the token is reused; slow form submission exceeds token lifetime; wrong secret key between environments (staging vs production); bots failing the challenge — the most common production occurrence.
Related errors
- failed to verify captcha solution: %w
- captcha verification failed
- failed to create Altcha challenge: %w
- failed to marshal Altcha challenge: %w
- no captcha provider enabled
AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01).
Data as JSON: /api/errors/6cc2c2d11e2805cb.
Report an issue: GitHub.