signalapp/Signal-Server · warning · ClientErrorException

409 Conflict (session already verified)

Error message

409 Conflict (session already verified)

What it means

requestVerificationCode throws this ClientErrorException with HTTP 409 CONFLICT when registrationServiceSession.verified() is true — the phone number has already been verified and a code cannot be requested again for that session. The response body includes the session state so the client can detect the already-verified condition and finish registration.

Solutions

  1. Treat the 409 as success: the number is already verified — proceed to the next registration step instead of requesting a code.
  2. Inspect the response body session JSON (verified flag) and update your client state machine to stop code requests once verified.
  3. If a new verification is genuinely needed, create a new verification session for the number.
  4. Guard client retry logic: don't re-call requestVerificationCode after a prior flow reported verification success.

Example fix

// before
await requestVerificationCode(sessionId, transport);
// after
const resp = await requestVerificationCode(sessionId, transport);
if (resp.status === 409) { proceedAsVerified(); return; }
Defensive patterns

Strategy: type-guard

Validate before calling

const s = await getSession(sessionId);
if (s.verified) { proceedAsVerified(); return; }

Type guard

function isSessionVerified(s) { return s && s.verified === true; }

Try / catch

try { await requestVerificationCode(...); } catch (e) {
  if (e.status === 409 && e.body?.session?.verified) { proceedAsVerified(); return; }
  throw e;
}

Prevention

When it happens

Trigger: POST to request a verification code for a session whose registration service session is already marked verified — e.g. calling requestVerificationCode again after a successful verification/registration check.

Common situations: Client retry logic re-sending a code request after verification already succeeded; double-submit from a UI; re-running an idempotency-unaware script; session reuse after the number was registered on another device.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

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

      final Optional<String> acceptLanguage,
      @NotNull @Valid final VerificationCodeRequest verificationCodeRequest,
      @Context final ContainerRequestContext requestContext) throws Throwable {

    final RegistrationServiceSession registrationServiceSession = retrieveRegistrationServiceSession(encodedSessionId);

    final VerificationSession verificationSession;
    {
      final VerificationSession storedVerificationSession = retrieveVerificationSession(registrationServiceSession);

      verificationSession =
          registrationFraudChecker.checkSendVerificationCodeAttempt(requestContext, storedVerificationSession,
                  registrationServiceSession.number())
              .updatedSession()
              .orElse(storedVerificationSession);
    }

    if (registrationServiceSession.verified()) {
      throw new ClientErrorException(
          Response.status(Response.Status.CONFLICT)
              .entity(buildResponse(registrationServiceSession, verificationSession))
              .build());
    }

    if (!verificationSession.allowedToRequestCode()) {
      final Response.Status status = verificationSession.requestedInformation().isEmpty()
          ? Response.Status.TOO_MANY_REQUESTS
          : Response.Status.CONFLICT;

      throw new ClientErrorException(
          Response.status(status)
              .entity(buildResponse(registrationServiceSession, verificationSession))
              .build());
    }

    final MessageTransport messageTransport = verificationCodeRequest.transport().toMessageTransport();

View on GitHub (pinned to 100ab61c82)