signalapp/Signal-Server · error · ServerErrorException

503 Service Unavailable

Error message

503 Service Unavailable

What it means

handleCaptcha wraps an IOException from the captcha assessment call (to the captcha provider, e.g. reCAPTCHA Enterprise) in a ServerErrorException with HTTP 503 SERVICE UNAVAILABLE. This means the server could not reach or get an answer from the captcha service, so verification cannot proceed at this moment. It is a transient upstream availability problem, not a client mistake.

Solutions

  1. Retry the updateSession request after a short backoff — 503 here is typically transient.
  2. Check service logs for 'error assessing captcha during registration verification' to see the underlying IOException cause.
  3. Verify outbound network access and DNS from the service host to the captcha provider endpoint.
  4. Confirm captcha provider credentials/endpoint configuration (captcha configuration in dynamic config) are valid and current.
Defensive patterns

Strategy: retry

Try / catch

try { await updateSession(...); } catch (e) {
  if (e.status === 503) { await sleep(backoff()); return updateSession(...); }
  throw e;
}

Prevention

When it happens

Trigger: updateSession with a captcha token while the call to the captcha assessment API throws IOException — network outage, DNS failure, TLS error, or the captcha provider returning an unreadable/unreachable response.

Common situations: Captcha provider outage or degraded network from the service host; egress firewall blocking the captcha API; expired/misconfigured service credentials causing connection resets; transient DNS failures in containerized environments.

Understand the failure class

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/f2297a2cbe9bf19d. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/VerificationController.java:482

              Optional.of(updateVerificationSessionRequest.captcha()), sourceHost, userAgent)
          .orElseThrow(() -> new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR));

      Metrics.counter(CAPTCHA_ATTEMPT_COUNTER_NAME, Tags.of(
              Tag.of(SUCCESS_TAG_NAME, String.valueOf(assessmentResult.isValid(captchaScoreThreshold))),
              UserAgentTagUtil.getPlatformTag(userAgent),
              Tag.of(COUNTRY_CODE_TAG_NAME, Util.getCountryCode(registrationServiceSession.number())),
              Tag.of(REGION_CODE_TAG_NAME, Util.getRegion(registrationServiceSession.number())),
              Tag.of(SCORE_TAG_NAME, assessmentResult.getScoreString())))
          .increment();

      CaptchaMetrics.measureCaptchaOutcome(assessmentResult.getNormalizedIntScore(),
          assessmentResult.isValid(captchaScoreThreshold),
          Util.getRegion(registrationServiceSession.number()),
          "verification");

    } catch (final IOException e) {
      logger.error("error assessing captcha during registration verification", e);
      throw new ServerErrorException(Response.Status.SERVICE_UNAVAILABLE, e);
    } catch (InvalidCaptchaArgumentException e) {
      throw new BadRequestException(e);
    }

    if (assessmentResult.isValid(captchaScoreThreshold)) {
      final List<VerificationSession.Information> submittedInformation = new ArrayList<>(
          verificationSession.submittedInformation());
      submittedInformation.add(VerificationSession.Information.CAPTCHA);

      final List<VerificationSession.Information> requestedInformation = new ArrayList<>(
          verificationSession.requestedInformation());
      // a captcha satisfies a push challenge, in case of push deliverability issues
      requestedInformation.remove(VerificationSession.Information.PUSH_CHALLENGE);
      final boolean allowedToRequestCode = (verificationSession.allowedToRequestCode()
          || requestedInformation.remove(VerificationSession.Information.CAPTCHA))
          && requestedInformation.isEmpty();

      verificationSession = new VerificationSession(verificationSession.sessionId(),

View on GitHub (pinned to 100ab61c82)