signalapp/Signal-Server · error · WebApplicationException

Missing required device capability

Error message

Missing required device capability

What it means

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).

Solutions

  1. Upgrade to a current client version that declares all required capabilities
  2. Explicitly include every capability in CAPABILITIES_REQUIRED_FOR_NEW_DEVICES in accountAttributes.capabilities
  3. Ensure accountAttributes (and its capabilities field) is non-null in the RegistrationRequest

Example fix

// before
attributes: { capabilities: ["transfer"] } // missing newer required caps
// after
attributes: { capabilities: [...DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES, "transfer"] }
Defensive patterns

Strategy: validation

Validate before calling

Set<DeviceCapability> caps = attributes.getCapabilities() == null ? Set.of() : attributes.getCapabilities();
if (!caps.containsAll(DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES)) {
  throw new IllegalStateException("missing required capabilities: " + Sets.difference(new HashSet<>(DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES), caps));
}

Type guard

boolean declaresRequiredCapabilities(AccountAttributes a) { return a.getCapabilities() != null && a.getCapabilities().containsAll(DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES); }

Try / catch

try { /* registration */ } catch (WebApplicationException e) { if (e.getResponse().getStatus() == 499) { upgradeClientAndDeclareRequiredCapabilities(); } }

Prevention

When it happens

Trigger: POST /v1/registration whose accountAttributes.capabilities is null or omits one or more of the capabilities listed in DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES.

Common situations: 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.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java:184

          required = true,
          schema = @Schema(type = "string")
      )
      @HeaderParam(HttpHeaders.AUTHORIZATION) @NotNull final BasicAuthorizationHeader authorizationHeader,
      @HeaderParam(HeaderUtils.X_SIGNAL_AGENT) final String signalAgent,
      @HeaderParam(HttpHeaders.USER_AGENT) final String userAgent,
      @NotNull @Valid final RegistrationRequest registrationRequest,
      @Context final ContainerRequestContext requestContext)
      throws RateLimitExceededException, InterruptedException, RegistrationLockFailureException {

    if (!registrationRequest.isEverySignedKeyValid(userAgent)) {
      throw new WebApplicationException("Invalid signature", 422);
    }

    if (!(registrationRequest.accountAttributes().getCapabilities() != null
        ? registrationRequest.accountAttributes().getCapabilities()
        : Collections.<DeviceCapability>emptySet()).containsAll(DeviceCapability.CAPABILITIES_REQUIRED_FOR_NEW_DEVICES)) {

      throw new WebApplicationException("Missing required device capability", 499);
    }

    final AccountCreationResponse response;

    if (registrationRequest.receiptCredentialPresentation() != null && registrationRequest.receiptCredentialPresentation().length > 0) {
      // A receipt credential presentation will only be present if the caller is trying to register a new account
      // without a phone number, in which case we can (and must) ignore the "username" in the authentication header.
      response = registerAccountWithoutNumber(authorizationHeader.getPassword(), registrationRequest, userAgent, signalAgent);
    } else {
      // The caller is either trying to register a new account with a phone number or recover an account by ACI. Either
      // way, the "username" from the auth header is meaningful.
      @Nullable UUID recoveredAccountIdentifier;

      try {
        recoveredAccountIdentifier = UUID.fromString(authorizationHeader.getUsername());
      } catch (final IllegalArgumentException _) {
        // The provided username is either a phone number or a nonsense string (for registration without a phone number)
        recoveredAccountIdentifier = null;

View on GitHub (pinned to 100ab61c82)