signalapp/Signal-Server · warning · BadRequestException
Received bad envelope type
Error message
Received bad envelope type {} from {} What it means
The message controller validates the envelope type byte on each incoming message before enqueueing it. If the client-supplied Envelope.Type value cannot be constructed (an unknown/invalid enum ordinal), an IllegalArgumentException is logged with the type value and user agent, and a 400 Bad Request is returned to the sender. This protects the queue from persisting envelopes that cannot be deserialized by recipients.
Solutions
- Update the client library/SDK to a version whose Envelope.Type enum matches the server's supported types.
- Check the logged type value and user agent to identify which client is sending invalid types.
- Validate message.type() client-side against the known enum range before sending.
- If this is a legitimate new type, deploy server support for the new Envelope.Type first.
Example fix
// before: sending a raw type byte from the client messageBuilder.setType(unknownTypeByte); // after: constrain to the known enum messageBuilder.setType(Envelope.Type.forName(typeString)); // or a validated constant
Defensive patterns
Strategy: validation
Validate before calling
// client-side, before sending
if (Envelope.Type.forNumber(typeByte) == null) {
throw new IllegalArgumentException("Unsupported envelope type: " + typeByte);
} Type guard
boolean isKnownEnvelopeType(byte t) { return Envelope.Type.forNumber(t) != null && Envelope.Type.forNumber(t) != Envelope.Type.UNRECOGNIZED; } Try / catch
try {
sendMessage(message);
} catch (BadRequestException e) {
logger.warn("server rejected envelope type {}", message.getType());
} Prevention
- Keep client and server proto/enum definitions in sync (same Envelope.Type set).
- Never send raw unvalidated type bytes; always construct types from the generated enum.
- Pin third-party client library versions known to match the server protocol version.
When it happens
Trigger: A client POSTs to /v1/messages (or the sealed sender / sync / story variants) with a message whose `type` field is a byte not corresponding to a known Envelope.Type enum constant.
Common situations: Outdated or third-party client libraries sending newer or garbage type bytes; protocol drift between client and server versions; corrupted or hand-crafted payloads during testing.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- Invalid payment method
- 400 Bad Request (INVALID_ARGUMENTS)
- 400 Bad Request
- enum field must be specified
- string expected to be non-empty
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/786b439998973b10.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java:394
messageByteLimitEstimator.add(destinationIdentifier.uuid().toString());
throw e;
}
final Map<Byte, Envelope> messagesByDeviceId = messages.messages().stream()
.collect(Collectors.toMap(IncomingMessage::destinationDeviceId, message -> {
try {
return message.toEnvelope(
destinationIdentifier,
sender != null ? new AciServiceIdentifier(sender.accountIdentifier()) : null,
sender != null ? sender.deviceId() : null,
messages.timestamp() == 0 ? System.currentTimeMillis() : messages.timestamp(),
isStory,
messages.online(),
messages.urgent(),
spamCheckResult.token().orElse(null),
clock);
} catch (final IllegalArgumentException e) {
logger.warn("Received bad envelope type {} from {}", message.type(), userAgent);
throw new BadRequestException(e);
}
}));
final Map<Byte, Integer> registrationIdsByDeviceId = messages.messages().stream()
.collect(Collectors.toMap(IncomingMessage::destinationDeviceId, IncomingMessage::destinationRegistrationId));
final Optional<Byte> syncMessageSenderDeviceId = messageType == MessageType.SYNC
? Optional.ofNullable(sender).map(AuthenticatedDevice::deviceId)
: Optional.empty();
try {
messageSender.sendMessages(destination,
destinationIdentifier,
messagesByDeviceId,
registrationIdsByDeviceId,
syncMessageSenderDeviceId,
userAgent);View on GitHub (pinned to 100ab61c82)