signalapp/Signal-Server · error · WebApplicationException

Multi-recipient messages must be addressed to ACI service…

Error message

Multi-recipient messages must be addressed to ACI service IDs

What it means

Multi-recipient messages may only be addressed to ACI (account identity) service IDs; PNIs (phone-number identities) are rejected. checkAccessKeys returns HTTP 401 with this message when any recipient in the payload uses a PNI.

Solutions

  1. Address every recipient by their ACI (UUID), not their PNI
  2. Look up the ACI from the contact/profile instead of deriving the service ID from the phone number
  3. Validate the recipient list client-side and strip/filter PNI entries before sending

Example fix

// before
if (serviceId instanceof ServiceId.Pni) { /* add recipient */ }
// after
if (serviceId instanceof ServiceId.Aci) { /* add recipient */ }
Defensive patterns

Strategy: validation

Validate before calling

function allAci(serviceIds) {
  return serviceIds.every(id => id.startsWith('00000000-0000-0000-0000-0000000000') === false && isAciUuid(id));
}
// or: verify ids came from ACI (identity key) lookups, not PNI (phone-number) lookups

Type guard

function isAci(serviceId) { return serviceId.type === 'ACI'; }

Try / catch

try { await send(msg); } catch (e) { if (e.status === 401 && /ACI service IDs/.test(e.message)) readdressToAciAndRetry(msg); else throw e; }

Prevention

When it happens

Trigger: Including a ServiceId.Pni in the recipient set of a POST to the multi-recipient message endpoint.

Common situations: Client resolving contacts by phone number and using the PNI instead of the ACI when building a sealed-sender story send.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java:716

  private void checkGroupSendToken(final Collection<ServiceId> recipients, final GroupSendFullToken groupSendFullToken) {
    try {
      groupSendFullToken.verify(recipients,
          clock.instant(),
          GroupSendDerivedKeyPair.forExpiration(groupSendFullToken.getExpiration(), serverSecretParams));
    } catch (final VerificationFailedException e) {
      throw new NotAuthorizedException(e);
    }
  }

  private void checkAccessKeys(
      final @NotNull CombinedUnidentifiedSenderAccessKeys accessKeys,
      final SealedSenderMultiRecipientMessage multiRecipientMessage,
      final Map<SealedSenderMultiRecipientMessage.Recipient, Account> resolvedRecipients) {

    if (multiRecipientMessage.getRecipients().keySet().stream()
        .anyMatch(serviceId -> serviceId instanceof ServiceId.Pni)) {

      throw new WebApplicationException("Multi-recipient messages must be addressed to ACI service IDs",
          Status.UNAUTHORIZED);
    }

    try {
      if (!UnidentifiedAccessUtil.checkUnidentifiedAccess(resolvedRecipients.values(), accessKeys.getAccessKeys())) {
        throw new WebApplicationException(Status.UNAUTHORIZED);
      }
    } catch (final IllegalArgumentException ignored) {
      throw new WebApplicationException(Status.UNAUTHORIZED);
    }
  }

  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  @Path("/report/{source}/{messageGuid}")
  public Response reportSpamMessage(
      @Auth AuthenticatedDevice auth,
      @PathParam("source") String source,

View on GitHub (pinned to 100ab61c82)