signalapp/Signal-Server · error · NotAuthorizedException
A group send endorsement token or unidentified access key…
Error message
A group send endorsement token or unidentified access key is required for non-story messages
What it means
Non-story multi-recipient messages require some form of authorization: either a group send endorsement token header or a combined unidentified-sender access key. Requests with neither receive a 401 (NotAuthorizedException). The server refuses unauthenticated non-story fan-out.
Solutions
- Obtain a group send endorsement token from the endorsements endpoint and attach it to the request.
- Alternatively compute and attach the combined unidentified-sender access keys header for the recipient set.
- Ensure the message is genuinely a story if you intend to send without authentication.
Example fix
// before
// no auth headers set for non-story group send -> 401
// after
GroupSendToken token = endorsementClient.fetchEndorsement(recipients);
builder.header("X-Group-Send-Token", token.serialize());
sendMultiRecipient(builder.build()); Defensive patterns
Strategy: try-catch
Validate before calling
if (!isStory && headers["X-Group-Send-Token"] == null && headers["X-Unidentified-Access-Keys"] == null) { await fetchEndorsements(recipients); } Type guard
function hasAuth(headers) { return headers["X-Group-Send-Token"] != null || headers["X-Unidentified-Access-Keys"] != null; } Try / catch
try { await sendMultiRecipient(msg); } catch (e) { if (e.status === 401 && /endorsement token or unidentified access key/.test(e.body)) { await fetchEndorsements(); retrySend(); } } Prevention
- Fetch group send endorsements before every non-story multi-recipient send
- Confirm proxies do not strip authentication headers
- Upgrade old clients that predate endorsement/access-key headers
When it happens
Trigger: POST to the multi-recipient endpoint with a non-story message where both the group send token header and the combined unidentified access key header are absent/null.
Common situations: Older client versions predating group send endorsements that never set the access-key header; header stripped by a proxy; client code path forgot to fetch endorsements before a group send.
Related errors
- Group send endorsement tokens should not be combined with…
- Only one of group send endorsement token and unidentified…
- access key length must be 16
- Invalid combined unidentified sender access keys
- Operation requires unauthenticated access
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/a6e1d819de77f655.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java:539
sample.stop(MULTI_RECIPIENT_MESSAGE_LATENCY_TIMER);
}
}
private SendMultiRecipientMessageResponse sendMultiRecipientMessage(final SealedSenderMultiRecipientMessage multiRecipientMessage,
final long timestamp,
final boolean ephemeral,
final boolean urgent,
@Nullable final GroupSendTokenHeader groupSendTokenHeader,
@Nullable final CombinedUnidentifiedSenderAccessKeys combinedUnidentifiedSenderAccessKeys,
final ContainerRequestContext context) {
// Perform fast, inexpensive checks before attempting to resolve recipients
if (MessageUtil.hasDuplicateDevices(multiRecipientMessage)) {
throw new BadRequestException("Multi-recipient message contains duplicate recipient");
}
if (groupSendTokenHeader == null && combinedUnidentifiedSenderAccessKeys == null) {
throw new NotAuthorizedException("A group send endorsement token or unidentified access key is required for non-story messages");
}
if (groupSendTokenHeader != null && combinedUnidentifiedSenderAccessKeys != null) {
throw new BadRequestException("Only one of group send endorsement token and unidentified access key may be provided");
}
if (groupSendTokenHeader != null) {
// Group send endorsements are checked before we even attempt to resolve any accounts, since
// the lists of service IDs in the envelope are all that we need to check against
checkGroupSendToken(multiRecipientMessage.getRecipients().keySet(), groupSendTokenHeader);
} else {
Metrics.counter(LEGACY_COMBINED_UAK_COUNTER_NAME, Tags.of(UserAgentTagUtil.getPlatformTag(context))).increment();
}
// At this point, the caller has at least superficially provided the information needed to send a multi-recipient
// message. Attempt to resolve the destination service identifiers to Signal accounts.
final Map<SealedSenderMultiRecipientMessage.Recipient, Account> resolvedRecipients =
MessageUtil.resolveRecipients(accountsManager, multiRecipientMessage);View on GitHub (pinned to 100ab61c82)