{"record":{"id":"cc5dc2fc390086d9","repo":"signalapp/Signal-Server","slug":"invalid-captcha-action","errorCode":null,"errorMessage":"invalid captcha action","messagePattern":"invalid captcha action","errorType":"validation","errorClass":"InvalidCaptchaArgumentException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/captcha/CaptchaChecker.java","lineNumber":98,"sourceCode":"      // full solution before proceeding\n      provider = prefix.substring(0, prefix.length() - SHORT_SUFFIX.length());\n      token = shortCodeExpander.retrieve(token).orElseThrow(() -> new InvalidCaptchaArgumentException(\"invalid shortcode\"));\n    }\n\n    final CaptchaClient client = this.captchaClientSupplier.apply(provider);\n    if (client == null) {\n      throw new InvalidCaptchaArgumentException(\"invalid captcha scheme\");\n    }\n\n    final Action parsedAction = Action.parse(action)\n        .orElseThrow(() -> {\n          Metrics.counter(INVALID_ACTION_COUNTER_NAME).increment();\n          return new InvalidCaptchaArgumentException(\"invalid captcha action\");\n        });\n\n    if (!parsedAction.equals(expectedAction)) {\n      Metrics.counter(INVALID_ACTION_COUNTER_NAME, \"action\", action).increment();\n      throw new InvalidCaptchaArgumentException(\"invalid captcha action\");\n    }\n\n    final Set<String> allowedSiteKeys = client.validSiteKeys(parsedAction);\n    if (!allowedSiteKeys.contains(siteKey)) {\n      logger.debug(\"invalid site-key {}, action={}\", siteKey, action);\n      Metrics.counter(INVALID_SITEKEY_COUNTER_NAME, \"action\", action).increment();\n      throw new InvalidCaptchaArgumentException(\"invalid captcha site-key\");\n    }\n\n    final AssessmentResult result = client.verify(maybeAci, siteKey, parsedAction, token, ip, userAgent);\n    Metrics.counter(ASSESSMENTS_COUNTER_NAME,\n            \"action\", action,\n            \"score\", result.getScoreString(),\n            \"provider\", provider)\n        .increment();\n    return result;\n  }\n}","sourceCodeStart":80,"sourceCodeEnd":116,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/captcha/CaptchaChecker.java#L80-L116","documentation":"After parsing the captcha token's intended Action, verify compares it to the action expected for the endpoint being called. A mismatch — or an unparseable action string — increments the invalid-action counter and throws InvalidCaptchaArgumentException('invalid captcha action'). The captcha must be solved for the specific action the API request declares.","triggerScenarios":"Calling an endpoint with expectedAction e.g. 'challenge' or 'registration' while the captcha token was solved for a different action, or passing a malformed/unrecognized action string to verify().","commonSituations":"Client solved a captcha on the wrong page/flow (e.g. recovery captcha reused for registration), API version changed action names and the client cached the old value, or a proxy/gateway forwards the wrong action parameter.","solutions":["Solve the captcha for the exact action the endpoint requires and pass that same action string in the request.","Update the client to the action names used by the current server API version.","Do not reuse captcha tokens across different endpoints or flows.","If server-side, confirm the expectedAction passed to verify() matches the endpoint's documented action."],"exampleFix":"// before\nString action = \"challenge\"; // solved for registration\n// after\nString action = \"registration\"; // matches expectedAction for this endpoint","handlingStrategy":"validation","validationCode":"if (!ACTIONS.includes(action)) throw new Error(`invalid captcha action: ${action}`);\nif (action !== expectedActionForEndpoint) throw new Error('captcha action mismatch');","typeGuard":"const isValidAction = (a) => typeof a === 'string' && ['registration','challenge','recovery'].includes(a);","tryCatchPattern":"try { await call(captcha, action); } catch (e) { if (e.message.includes('invalid captcha action')) { return retryWithFreshCaptcha(expectedAction); } throw e; }","preventionTips":["Solve the captcha on the flow that matches the endpoint being called","Never reuse captcha tokens across endpoints","Track action-name changes between server versions"],"tags":["captcha","validation","api"],"backgroundTag":"invalid-enum-value","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}