{"record":{"id":"b92850ec740ff3ce","repo":"signalapp/Signal-Server","slug":"status-410","errorCode":null,"errorMessage":".status(410)","messagePattern":"\\.status\\(410\\)","errorType":"http","errorClass":"WebApplicationException","httpStatus":410,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java","lineNumber":685,"sourceCode":"              .toList();\n\n      if (!accountMismatchedDevices.isEmpty()) {\n        throw new WebApplicationException(Response\n            .status(409)\n            .type(MediaType.APPLICATION_JSON_TYPE)\n            .entity(accountMismatchedDevices)\n            .build());\n      }\n\n      final List<AccountStaleDevices> accountStaleDevices =\n          e.getMismatchedDevicesByServiceIdentifier().entrySet().stream()\n              .filter(entry -> !entry.getValue().staleDeviceIds().isEmpty())\n              .map(entry -> new AccountStaleDevices(entry.getKey(),\n                  new StaleDevicesResponse(entry.getValue().staleDeviceIds())))\n              .toList();\n\n      throw new WebApplicationException(Response\n          .status(410)\n          .type(MediaType.APPLICATION_JSON)\n          .entity(accountStaleDevices)\n          .build());\n    } catch (final MessageDeliveryNotAllowedException e) {\n      throw new ServiceUnavailableException();\n    }\n  }\n\n  private void checkGroupSendToken(final Collection<ServiceId> recipients, final GroupSendTokenHeader groupSendToken) {\n    checkGroupSendToken(recipients, groupSendToken.token());\n  }\n\n  private void checkGroupSendToken(final Collection<ServiceId> recipients, final GroupSendFullToken groupSendFullToken) {\n    try {\n      groupSendFullToken.verify(recipients,\n          clock.instant(),\n          GroupSendDerivedKeyPair.forExpiration(groupSendFullToken.getExpiration(), serverSecretParams));\n    } catch (final VerificationFailedException e) {","sourceCodeStart":667,"sourceCodeEnd":703,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java#L667-L703","documentation":"sendMultiRecipientMessage returns HTTP 410 Gone when one or more recipient accounts report stale devices: the sender encrypted for device IDs the server has retired. The body lists AccountStaleDevices with each account's staleDeviceIds so the sender can discard sessions for those devices and resend without them.","triggerScenarios":"POSTing a multi-recipient message containing ciphertext for device IDs that were since deactivated (device unlinked, account re-registered, device retired).","commonSituations":"Recipients re-registering (which invalidates all old device IDs) or unlinking tablets/desktops; senders with cached session state from before a recipient's device rotation; long-offline clients catching up on group messages.","solutions":["Discard local sessions for the staleDeviceIds in the 410 body and rebuild sessions from fresh prekeys","Re-encrypt and resend the message excluding stale devices","Clear cached recipient key material and refetch prekeys for accounts named in the response","Treat repeated 410s as a signal to resync the group's device lists"],"exampleFix":"// before\nsendMessage(multiRecipientPayload);\n// after\ntry { sendMessage(multiRecipientPayload); }\ncatch (StaleDevices410 e) {\n  for (AccountStaleDevices a : parseStale(e.body())) {\n    a.staleDevices().forEach(id -> sessionStore.deleteSession(a.accountId(), id));\n  }\n  resend();\n}","handlingStrategy":"fallback","validationCode":"// evict known-retired device ids from the send set before encrypting\nrecipients.forEach(r -> sendSet.removeAll(retiredDeviceCache.get(r.accountId())));","typeGuard":null,"tryCatchPattern":"if (response.code() == 410) {\n  parseStaleDevices(response.body()).forEach(a ->\n      a.staleDeviceIds().forEach(id -> sessionStore.delete(a.accountId(), id)));\n  reencryptAndResend();\n}","preventionTips":["Delete sessions for retired device IDs as soon as a 410 lists them","Refetch prekeys after any recipient re-registration","Don't cache recipient device lists across long offline periods","Distinguish 409 (missing+extra) from 410 (stale) handling"],"tags":["http","encryption","stale-devices","signal-protocol"],"backgroundTag":"http-error-response","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"}