signalapp/Signal-Server · error · WebApplicationException

use websockets

Error message

use websockets

What it means

RestDeprecationFilter rejects REST API calls from clients whose reported version is at or above the minimumRestFreeVersion, pushing them to the Signal websocket transport. It throws WebApplicationException with custom status 498 and message 'use websockets', while incrementing a metrics counter tagged by platform and version. Unknown user agents are ignored (allowed through).

Solutions

  1. Upgrade the client library/app to a version that uses the websocket transport
  2. If you maintain a third-party client, implement the WebSocket-based signaling protocol
  3. If operating the server, raise (or disable) minimumRestFreeVersion in RestDeprecationConfiguration to allow older clients temporarily

Example fix

// before (config)
minimumRestFreeVersion: 710
// after (temporarily allow old clients)
minimumRestFreeVersion: 999999
Defensive patterns

Strategy: validation

Validate before calling

String ua = "Signal-Android/7.10.2";
if (UserAgentUtil.parseUserAgentString(ua).map(p -> p.version().compareTo(minimumRestFreeVersion) >= 0).orElse(false)) {
  throw new UnsupportedOperationException("REST deprecated for this version; use websockets");
}

Try / catch

try {
  restClient.sendMessage(message);
} catch (WebApplicationException e) {
  if (e.getResponse().getStatus() == 498) {
    // fall back to websocket transport
  }
}

Prevention

When it happens

Trigger: Any REST request whose User-Agent is a recognized platform with a parsed version >= minimumRestFreeVersion configured in RestDeprecationConfiguration; unauthenticated and authenticated requests both blocked.

Common situations: Older Signal-Android/iOS releases after the server enables REST deprecation; third-party clients (e.g. signal-cli, libraries) that still use REST endpoints; local tools using outdated API clients.

Understand the failure class

Background: "is deprecated and will be removed" — deprecation warnings for old API names, keywords, and options, and how to migrate before the removal release — this error's family across 29 libraries.

Related errors


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

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/filters/RestDeprecationFilter.java:81

    final String userAgentString = requestContext.getHeaderString(HttpHeaders.USER_AGENT);

    try {
      final UserAgent userAgent = UserAgentUtil.parseUserAgentString(userAgentString);
      final ClientPlatform platform = userAgent.platform();
      final Semver version = userAgent.version();
      final PlatformConfiguration config = dynamicConfigurationManager.getConfiguration().restDeprecation().platforms().get(platform);
      if (config == null) {
        return;
      }
      if (!isEnrolled(requestContext, config.universalRolloutPercent())) {
        return;
      }
      if (version.isGreaterThanOrEqualTo(config.minimumRestFreeVersion())) {
        Metrics.counter(
            DEPRECATED_REST_COUNTER_NAME, Tags.of("platform", platform.name().toLowerCase(), "version", version.toString()))
            .increment();
        throw new WebApplicationException("use websockets", 498);
      }
    } catch (final UnrecognizedUserAgentException e) {
      return;                   // at present we're only interested in experimenting on known clients
    }
  }

  private boolean isEnrolled(final ContainerRequestContext requestContext, int universalRolloutPercent) {
    if (random.get().nextInt(100) < universalRolloutPercent) {
      return true;
    }

    final SecurityContext securityContext = requestContext.getSecurityContext();

    if (securityContext == null || securityContext.getUserPrincipal() == null) {
      return false;
    }

    if (securityContext.getUserPrincipal() instanceof AuthenticatedDevice authenticatedDevice) {

View on GitHub (pinned to 100ab61c82)