signalapp/Signal-Server · error · InvalidCaptchaArgumentException
invalid captcha scheme
Error message
invalid captcha scheme
What it means
CaptchaChecker.verify resolves a captcha provider from the token's prefix (e.g. 'signal-recaptcha:' or a short code) and looks up the registered CaptchaClient for that scheme. If no client is registered for the resolved provider, the token's scheme is not supported by this server configuration, so verify throws InvalidCaptchaArgumentException('invalid captcha scheme').
Solutions
- Add the matching captcha provider configuration (API keys/site keys for recaptcha or hcaptcha) to the server config so a CaptchaClient is registered for the token's scheme.
- Have the client fetch a fresh captcha token from the correct provider used by this server deployment.
- Check the token format: it must carry a recognized provider prefix (or a valid short code) before the ':' separator.
- Verify no environment mismatch: token issued by a staging server won't validate on prod.
Example fix
// before (client sends unsupported scheme) captchaToken = "hcaptcha:" + hcaptchaResponse; // after (match server-configured provider) captchaToken = "signal-recaptcha:" + grecaptchaResponse;
Defensive patterns
Strategy: validation
Validate before calling
const scheme = captchaToken.slice(0, captchaToken.lastIndexOf(':'));
if (!SUPPORTED_SCHEMES.has(scheme)) throw new Error(`unsupported captcha scheme: ${scheme}`); Type guard
const isSupportedScheme = (t) => typeof t === 'string' && SUPPORTED_SCHEMES.has(t.slice(0, t.lastIndexOf(':'))); Try / catch
try { await register(captchaToken); } catch (e) { if (e.message.includes('invalid captcha scheme')) { await refreshCaptchaFromSupportedProvider(); } else throw e; } Prevention
- Always fetch captcha tokens from the provider configured on the server you target
- Keep the provider prefix on the token intact when passing it to the API
- Align captcha config across environments before deploying clients
When it happens
Trigger: POST /v1/registration or /v1/accounts with a captcha token whose provider prefix (text before the final ':') does not match any configured CaptchaClient (e.g. token prefixed 'hcaptcha:' when only reCAPTCHA is configured, or a token with no recognizable prefix).
Common situations: Server launched without captcha API keys in config (captcha config block missing), clients from a newer app version sending a captcha scheme the server doesn't support yet, tokens copied from a different environment (staging vs prod), or a region/proxy that issues short codes the expander can't resolve.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
- invalid captcha site-key
- too few parts
- invalid captcha action
- 400 Bad Request
- target-queue-size-bytes must be positive
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/a50ca2bcff56b150.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/captcha/CaptchaChecker.java:87
throw new InvalidCaptchaArgumentException("too few parts");
}
final String prefix = parts[0];
final String siteKey = parts[1].toLowerCase(Locale.ROOT).strip();
final String action = parts[2];
String token = parts[3];
String provider = prefix;
if (prefix.endsWith(SHORT_SUFFIX)) {
// This is a "short" solution that points to the actual solution. We need to fetch the
// full solution before proceeding
provider = prefix.substring(0, prefix.length() - SHORT_SUFFIX.length());
token = shortCodeExpander.retrieve(token).orElseThrow(() -> new InvalidCaptchaArgumentException("invalid shortcode"));
}
final CaptchaClient client = this.captchaClientSupplier.apply(provider);
if (client == null) {
throw new InvalidCaptchaArgumentException("invalid captcha scheme");
}
final Action parsedAction = Action.parse(action)
.orElseThrow(() -> {
Metrics.counter(INVALID_ACTION_COUNTER_NAME).increment();
return new InvalidCaptchaArgumentException("invalid captcha action");
});
if (!parsedAction.equals(expectedAction)) {
Metrics.counter(INVALID_ACTION_COUNTER_NAME, "action", action).increment();
throw new InvalidCaptchaArgumentException("invalid captcha action");
}
final Set<String> allowedSiteKeys = client.validSiteKeys(parsedAction);
if (!allowedSiteKeys.contains(siteKey)) {
logger.debug("invalid site-key {}, action={}", siteKey, action);
Metrics.counter(INVALID_SITEKEY_COUNTER_NAME, "action", action).increment();
throw new InvalidCaptchaArgumentException("invalid captcha site-key");View on GitHub (pinned to 100ab61c82)