{"record":{"id":"9331dc5d7161334a","repo":"signalapp/Signal-Server","slug":"registration-session-is-unverified","errorCode":null,"errorMessage":"registration session is unverified","messagePattern":"registration session is unverified","errorType":"http","errorClass":"NotAuthorizedException","httpStatus":401,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java","lineNumber":138,"sourceCode":"        throw new WebApplicationException(Response.status(410)\n            .type(MediaType.APPLICATION_JSON)\n            .entity(new StaleDevicesResponse(e.getMismatchedDevices().staleDeviceIds()))\n            .build());\n      } else {\n        throw new WebApplicationException(Response.status(409)\n            .type(MediaType.APPLICATION_JSON_TYPE)\n            .entity(new MismatchedDevicesResponse(e.getMismatchedDevices().missingDeviceIds(),\n                e.getMismatchedDevices().extraDeviceIds()))\n            .build());\n      }\n    } catch (final IllegalArgumentException e) {\n      throw new BadRequestException(e);\n    } catch (final MessageTooLargeException e) {\n      throw new WebApplicationException(Response.Status.REQUEST_ENTITY_TOO_LARGE);\n    } catch (final MessageDeliveryNotAllowedException e) {\n      throw new ServiceUnavailableException();\n    } catch (final UnverifiedRegistrationSessionException e) {\n      throw new NotAuthorizedException(\"registration session is unverified\");\n    } catch (final InvalidRegistrationSessionException e) {\n      throw new BadRequestException(e.getMessage());\n    } catch (final IOException e) {\n      throw new ServiceUnavailableException(e.getMessage());\n    } catch (final RecoveryPasswordVerificationFailedException e) {\n      throw new ForbiddenException(\"recovery password could not be verified\");\n    }\n  }\n\n  @PUT\n  @Path(\"/phone_number_discoverability\")\n  @Consumes(MediaType.APPLICATION_JSON)\n  @Produces(MediaType.APPLICATION_JSON)\n  @Operation(summary = \"Sets whether the account should be discoverable by phone number in the directory.\")\n  @ApiResponse(responseCode = \"204\", description = \"The setting was successfully updated.\")\n  public void setPhoneNumberDiscoverability(\n      @Auth AuthenticatedDevice auth,\n      @NotNull @Valid PhoneNumberDiscoverabilityRequest phoneNumberDiscoverability) {","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountControllerV2.java#L120-L156","documentation":"During changeNumber, the registration session tied to the new phone number must be verified (e.g. SMS/voice challenge completed). If the RegistrationService reports UnverifiedRegistrationSessionException, the server responds 401 NotAuthorizedException('registration session is unverified'), meaning the session exists but its verification step was never completed.","triggerScenarios":"PUT /v2/accounts/phone_number/{number} where the referenced registration session (session id in request) has not passed the required verification challenge for the new number.","commonSituations":"Client submitted the change-number request before the user entered the SMS code, session verification expired, or the client used a session created for a different flow/number.","solutions":["Complete the registration session verification (submit the SMS/voice code) via the registration service before calling change-number.","Create a fresh registration session, verify it, then retry the change-number request with that session id.","Check session expiry and restart the flow if the verification window lapsed.","Ensure the session id sent matches the session used to verify the new number."],"exampleFix":"// before\nchangeNumber(sessionId); // session created but code never submitted\n// after\nregistrationClient.verifySession(sessionId, smsCode);\nchangeNumber(sessionId);","handlingStrategy":"retry","validationCode":"const session = await getSession(sessionId);\nif (session.status !== 'verified') throw new Error('complete session verification first');","typeGuard":"const isVerifiedSession = (s) => s && s.verified === true;","tryCatchPattern":"try { await changeNumber(req); } catch (e) { if (e.status === 401 && e.message.includes('unverified')) { await submitVerificationCode(sessionId, code); return changeNumber(req); } throw e; }","preventionTips":["Always complete the SMS/voice challenge before submitting change-number","Create fresh sessions when verification windows expire","Ensure the session id matches the number being changed"],"tags":["registration","session","unauthorized"],"backgroundTag":"verification-session-not-completed","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"}