signalapp/Signal-Server · warning · ClientErrorException

?…

Error message

? Response.status(418).entity(verificationSessionResponse).build()

What it means

requestVerificationCode returns HTTP 418 ("I'm a teapot", Signal's convention for transport-not-allowed) when the registration service rejects the requested verification transport (e.g. voice call or SMS) for this session/number. If the exception carries a registration session, the client gets a 418 (TransportNotAllowedException) or 409 (other RegistrationServiceException) with an updated VerificationSessionResponse body; otherwise it gets 404.

Solutions

  1. Switch to a different transport (e.g. request a voice call instead of SMS) for this session
  2. Use the returned verificationSessionResponse to continue the existing session rather than starting a new request
  3. Wait and retry later if the transport is temporarily blocked by fraud rules
  4. Check registration-service configuration/allow-lists if a region should permit the transport

Example fix

// before: retry same transport forever
requestCode(phoneNumber, Transport.SMS);
// after
if (response.code() == 418) {
  updateSessionFromResponse(response.body());
  requestCode(phoneNumber, Transport.VOICE); // try alternate transport
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check whether the session allows the requested transport first
if (!session.getAllowedTransports().contains(requestedTransport)) {
  requestedTransport = session.getAllowedTransports().iterator().next();
}

Try / catch

try { requestVerificationCode(session, transport); }
catch (ClientErrorException e) {
  if (e.getResponse().getStatus() == 418) {
    updateSession(e.getResponse().readEntity(VerificationSessionResponse.class));
    switchTransportAndRetry();
  }
}

Prevention

When it happens

Trigger: POST/PUT to request a verification code where the registration service refuses the chosen channel — e.g. requesting SMS for a number flagged to only allow voice, or a transport blocked by fraud detection for that session.

Common situations: Numbers in regions where SMS delivery is disabled/blocked; carriers/routes flagged by fraud prevention; clients retrying the same disallowed transport in a loop instead of switching channel or completing an existing session.

Related errors


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

Appendix: source

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

    final RegistrationServiceSession resultSession;
    try {
      resultSession = registrationServiceClient.sendVerificationCode(registrationServiceSession.id(),
          messageTransport,
          clientType,
          acceptLanguage.orElse(null),
          senderOverride,
          REGISTRATION_RPC_TIMEOUT);
    } catch (final VerificationSessionRateLimitExceededException e) {
      throw new ClientErrorException(buildResponseForRateLimitExceeded(verificationSession,
          e.getRegistrationSession(),
          e.getRetryDuration()));
    } catch (final RegistrationServiceException registrationServiceException) {
      throw registrationServiceException.getRegistrationSession()
          .map(s -> buildResponse(s, verificationSession))
          .map(verificationSessionResponse -> {
            final Response response = registrationServiceException instanceof TransportNotAllowedException
                ? Response.status(418).entity(verificationSessionResponse).build()
                : Response.status(Response.Status.CONFLICT).entity(verificationSessionResponse).build();

            return new ClientErrorException(response);
          })
          .orElseGet(NotFoundException::new);
    } catch (final RegistrationFraudException e) {
      if (dynamicConfigurationManager.getConfiguration().getRegistrationConfiguration()
          .squashDeclinedAttemptErrors()) {
        return buildResponse(registrationServiceSession, verificationSession);
      } else {
        throw e.getCause();
      }
    } catch (final RuntimeException e) {
      logger.error("Registration service failure", e);
      throw new ServerErrorException(Response.Status.INTERNAL_SERVER_ERROR);
    }

    accountsManager.getByE164(registrationServiceSession.number())

View on GitHub (pinned to 100ab61c82)