signalapp/Signal-Server · error · InvalidCaptchaArgumentException
too few parts
Error message
too few parts
What it means
InvalidCaptchaArgumentException thrown by CaptchaChecker.verify when the captcha input string splits into fewer than 4 parts on the SEPARATOR. A valid input carries prefix, siteKey, action, and token segments; anything shorter cannot be parsed and is rejected before any challenge verification is attempted. The parse also normalizes the siteKey to lowercase and strips whitespace, expecting exact structural input.
Solutions
- Send the full 4-segment string: prefix + separator + siteKey + separator + action + separator + token
- Update old clients to the current captcha input format (4 parts)
- Validate the segment count client-side before calling the verification endpoint
- Log the received input segment count (never the token) to spot format drift
Example fix
// before String input = rawChallengeToken; // 1 part checker.verify(remoteAddr, input, userAgent); // too few parts // after String input = "signalcaptcha" + SEPARATOR + siteKey + SEPARATOR + action + SEPARATOR + rawChallengeToken; checker.verify(remoteAddr, input, userAgent);
Defensive patterns
Strategy: validation
Validate before calling
String[] parts = captchaInput.split("\\" + SEPARATOR, 4);
if (parts.length < 4 || parts[3].isBlank()) {
throw new IllegalArgumentException("captcha input must be prefix"
+ SEPARATOR + "siteKey" + SEPARATOR + "action" + SEPARATOR + "token");
} Try / catch
try { checker.verify(ip, input, userAgent); }
catch (InvalidCaptchaArgumentException e) {
respondCaptchaFormatError(e); // ask client to resend the full 4-part captcha string
} Prevention
- Always send the full prefixed captcha string, never the raw provider token
- Count separator-delimited segments client-side before submission
- Keep client captcha format templates in sync with server expectations
- Strip nothing from the token segment when assembling the input
When it happens
Trigger: Calling verify with a captcha token string missing the prefix, site key, action, or token segment — e.g. passing only the raw hCaptcha/Turnstile response token, or a string where the separator character does not appear enough times.
Common situations: Clients submitting the challenge provider's token directly instead of the full prefixed captcha string the Signal client assembles; old clients producing a 3-part format when the server expects 4; copy-pasted tokens with segments stripped; misconfigured client templates dropping the action segment.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/e12eddff89f42d84.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/captcha/CaptchaChecker.java:69
* expected format is {@code version-prefix.sitekey.action.token}
* @param ip IP of the solver
* @param userAgent User-Agent of the solver
* @return An {@link AssessmentResult} indicating whether the solution should be accepted, and a score that can be
* used for metrics
* @throws IOException if there is an error validating the captcha with the underlying service
* @throws InvalidCaptchaArgumentException if input is not in the expected format
*/
public AssessmentResult verify(
final Optional<UUID> maybeAci,
final Action expectedAction,
final String input,
final String ip,
@Nullable final String userAgent) throws IOException, InvalidCaptchaArgumentException {
final String[] parts = input.split("\\" + SEPARATOR, 4);
// we allow missing actions, if we're missing 1 part, assume it's the action
if (parts.length < 4) {
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");View on GitHub (pinned to 100ab61c82)