knadh/listmonk · warning

captcha verification failed

Error message

captcha verification failed

What it means

verifyAltcha returns this when altcha.VerifySolution successfully evaluates the payload but reports the proof-of-work solution is invalid — the client did not solve the challenge correctly, or the payload was signed with a different HMAC key than the server now holds. It is the expected rejection for non-solved or tampered Altcha payloads.

Source

Thrown at internal/captcha/captcha.go:194

		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 {
		return fmt.Errorf("captcha token already used"), false
	}
	tmptokens.Set(payload, 5*time.Minute, nil)

	return nil, true
}

View on GitHub (pinned to 670c01717d)

Solutions

  1. Treat it as a client error: return 400/re-serve the form with a fresh challenge and let the user retry.
  2. Confirm the HMAC key hasn't changed since challenges were issued; if rotated, old in-flight challenges fail — restart form flows after rotation.
  3. Log the payload's challenge/salt fields (not the payload itself) to distinguish tampering from key mismatch.
  4. Ensure the client widget actually completed the proof-of-work before submission (check altcha 'verified' state).

Example fix

// before: key rotated in config without invalidating sessions
// after: version the key and accept only current-generation challenges
const keyVersion = 2
if challengeKeyVersion(payload) != keyVersion {
  return errors.New("stale captcha challenge, reload the page"), false
}
Defensive patterns

Strategy: fallback

Validate before calling

if err, ok := captcha.Verify(payload); !ok {
  // re-issue a fresh challenge so the user can retry with new proof-of-work
  challenge, err := captcha.GenerateChallenge(r.Context())
  if err != nil {
    http.Error(w, "captcha unavailable", http.StatusServiceUnavailable)
    return
  }
  renderFormWithChallenge(w, challenge)
  return
}

Try / catch

if err, ok := captcha.Verify(payload); !ok {
  if err != nil && err.Error() == "captcha verification failed" {
    // solution invalid: serve a new challenge and ask the user to retry
    http.Error(w, "captcha failed — reload and try again", http.StatusBadRequest)
    return
  }
}

Prevention

When it happens

Trigger: Verify is called with a payload whose computed hash does not meet the challenge target, whose salt/algorithm fields were modified, or which was generated under an old HMAC key after a server-side key rotation.

Common situations: Bots submitting random payloads without solving proof-of-work (the dominant case); users bypassing the widget and posting raw form data; HMAC key changed on the server while clients still hold challenges from before the restart; complexity raised so slow clients time out and submit partial solutions.

Related errors


AI-assisted analysis of knadh/listmonk@670c01717d (2026-09-01). Data as JSON: /api/errors/9f35923269a0c631. Report an issue: GitHub.