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
- Upgrade the client library/app to a version that uses the websocket transport
- If you maintain a third-party client, implement the WebSocket-based signaling protocol
- 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
- Keep Signal clients updated so they use websocket transport
- Check the server's RestDeprecationConfiguration when building third-party clients
- Watch for HTTP 498 as a signal to migrate off REST
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
- User-Agent string is blank
- Missing required request attributes!
- Missing required response attributes!
- sendErrorResponse(requestMessage…
- failed to make http request to Cloudflare Turn
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)