{"record":{"id":"f65072b99ed96a3b","repo":"signalapp/Signal-Server","slug":"missing-required-device-capability-f65072","errorCode":null,"errorMessage":"Missing required device capability","messagePattern":"Missing required device capability","errorType":"http","errorClass":"WebApplicationException","httpStatus":499,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java","lineNumber":184,"sourceCode":"          required = true,\n          schema = @Schema(type = \"string\")\n      )\n      @HeaderParam(HttpHeaders.AUTHORIZATION) @NotNull final BasicAuthorizationHeader authorizationHeader,\n      @HeaderParam(HeaderUtils.X_SIGNAL_AGENT) final String signalAgent,\n      @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent,\n      @NotNull @Valid final RegistrationRequest registrationRequest,\n      @Context final ContainerRequestContext requestContext)\n      throws RateLimitExceededException, InterruptedException, RegistrationLockFailureException {\n\n    if (!registrationRequest.isEverySignedKeyValid(userAgent)) {\n      throw new WebApplicationException(\"Invalid signature\", 422);\n    }\n\n    if (!(registrationRequest.accountAttributes().getCapabilities() != null\n        ? registrationRequest.accountAttributes().getCapabilities()\n        : Collections.<DeviceCapability>emptySet()).containsAll(DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES)) {\n\n      throw new WebApplicationException(\"Missing required device capability\", 499);\n    }\n\n    final AccountCreationResponse response;\n\n    if (registrationRequest.receiptCredentialPresentation() != null && registrationRequest.receiptCredentialPresentation().length > 0) {\n      // A receipt credential presentation will only be present if the caller is trying to register a new account\n      // without a phone number, in which case we can (and must) ignore the \"username\" in the authentication header.\n      response = registerAccountWithoutNumber(authorizationHeader.getPassword(), registrationRequest, userAgent, signalAgent);\n    } else {\n      // The caller is either trying to register a new account with a phone number or recover an account by ACI. Either\n      // way, the \"username\" from the auth header is meaningful.\n      @Nullable UUID recoveredAccountIdentifier;\n\n      try {\n        recoveredAccountIdentifier = UUID.fromString(authorizationHeader.getUsername());\n      } catch (final IllegalArgumentException _) {\n        // The provided username is either a phone number or a nonsense string (for registration without a phone number)\n        recoveredAccountIdentifier = null;","sourceCodeStart":166,"sourceCodeEnd":202,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java#L166-L202","documentation":"New accounts must declare support for a minimum set of device capabilities (DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES) in their accountAttributes. If the declared capability set is missing any required capability, registration is rejected with HTTP 499 'Missing required device capability'. This ensures newly registered accounts are compatible with mandatory protocol features (e.g. required capabilities for new devices).","triggerScenarios":"POST /v1/registration whose accountAttributes.capabilities is null or omits one or more of the capabilities listed in DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES.","commonSituations":"Old or outdated client versions that don't know about newer mandatory capabilities; clients that send an empty/null capabilities set; custom or forked clients not updated when new required capabilities were introduced.","solutions":["Upgrade to a current client version that declares all required capabilities","Explicitly include every capability in CAPABILITIES_REQUIRED_FOR_NEW_DEVICES in accountAttributes.capabilities","Ensure accountAttributes (and its capabilities field) is non-null in the RegistrationRequest"],"exampleFix":"// before\nattributes: { capabilities: [\"transfer\"] } // missing newer required caps\n// after\nattributes: { capabilities: [...DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES, \"transfer\"] }","handlingStrategy":"validation","validationCode":"Set<DeviceCapability> caps = attributes.getCapabilities() == null ? Set.of() : attributes.getCapabilities();\nif (!caps.containsAll(DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES)) {\n  throw new IllegalStateException(\"missing required capabilities: \" + Sets.difference(new HashSet<>(DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES), caps));\n}","typeGuard":"boolean declaresRequiredCapabilities(AccountAttributes a) { return a.getCapabilities() != null && a.getCapabilities().containsAll(DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES); }","tryCatchPattern":"try { /* registration */ } catch (WebApplicationException e) { if (e.getResponse().getStatus() == 499) { upgradeClientAndDeclareRequiredCapabilities(); } }","preventionTips":["Always populate accountAttributes.capabilities from the current library's constant set","Keep clients updated so new mandatory capabilities are declared automatically","Fail fast client-side when the capabilities set is null or empty"],"tags":["registration","capabilities","validation","client-version"],"backgroundTag":"schema-validation-failed","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"}