{"record":{"id":"c986a2a8a0939e02","repo":"signalapp/Signal-Server","slug":"group-send-token-not-allowed-when-sending-stories","errorCode":null,"errorMessage":"Group send token not allowed when sending stories","messagePattern":"Group send token not allowed when sending stories","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java","lineNumber":508,"sourceCode":"\n    if (timestamp < 0 || timestamp > MAX_TIMESTAMP) {\n      throw new BadRequestException(\"Illegal timestamp\");\n    }\n\n    if (multiRecipientMessage.getRecipients().isEmpty()) {\n      throw new BadRequestException(\"Recipient list is empty\");\n    }\n\n    final Timer.Sample sample = Timer.start();\n\n    try {\n      final SendMultiRecipientMessageResponse sendMultiRecipientMessageResponse;\n\n      if (isStory) {\n        if (groupSendToken != null) {\n          // Stories require no authentication. We fail requests that provide a groupSendToken, but for historical\n          // reasons we allow requests to set a combined access key, even though we ignore it\n          throw new BadRequestException(\"Group send token not allowed when sending stories\");\n        }\n\n        sendMultiRecipientMessageResponse =\n            sendMultiRecipientStoryMessage(multiRecipientMessage, timestamp, online, isUrgent, context);\n      } else {\n        sendMultiRecipientMessageResponse =\n            sendMultiRecipientMessage(multiRecipientMessage, timestamp, online, isUrgent, groupSendToken, accessKeys,\n                context);\n      }\n\n      return Response.ok(sendMultiRecipientMessageResponse).build();\n    } finally {\n      sample.stop(MULTI_RECIPIENT_MESSAGE_LATENCY_TIMER);\n    }\n  }\n\n  private SendMultiRecipientMessageResponse sendMultiRecipientMessage(final SealedSenderMultiRecipientMessage multiRecipientMessage,\n      final long timestamp,","sourceCodeStart":490,"sourceCodeEnd":526,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java#L490-L526","documentation":"Stories on the multi-recipient path require no authentication, so providing a group send token header with a story send is rejected with a 400. (For historical reasons a combined unidentified-access key is tolerated but ignored.)","triggerScenarios":"POST to the multi-recipient endpoint with the story flag set and the group send endorsement token header present.","commonSituations":"Client attaches group send tokens to every multi-recipient send including stories; story support added to a code path that always sets the token header; reused request builders.","solutions":["Do not attach the group send token header when the message is a story.","Route story sends through the story-specific path without endorsement headers.","Audit client request-building code to conditionally set the token based on message type."],"exampleFix":"// before\nbuilder.header(\"X-Group-Send-Token\", token);\nsendMultiRecipient(payload, /* isStory */ true);\n// after\nif (!isStory) { builder.header(\"X-Group-Send-Token\", token); }\nsendMultiRecipient(payload, isStory);","handlingStrategy":"validation","validationCode":"if (isStory && headers[\"X-Group-Send-Token\"]) { delete headers[\"X-Group-Send-Token\"]; }","typeGuard":null,"tryCatchPattern":"try { await sendMultiRecipient(payload, isStory); } catch (e) { if (e.status === 400 && /not allowed when sending stories/.test(e.body)) { resendWithoutToken(); } }","preventionTips":["Only attach group send tokens for non-story sends","Keep story sends on a dedicated request path","Cover story sends in integration tests to catch header leakage"],"tags":["http-400","stories","group-send-endorsements"],"backgroundTag":"mutually-exclusive-options","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"}