signalapp/Signal-Server · error · BadRequestException
Illegal timestamp
Error message
Illegal timestamp
What it means
The multi-recipient (sealed sender) message endpoint validates the message timestamp against 0 <= timestamp <= MAX_TIMESTAMP; out-of-range values are rejected with a 400. This guards against negative or absurdly far-future timestamps from faulty or malicious clients.
Solutions
- Check the client's system clock and sync via NTP before sending.
- Clamp/validate the timestamp client-side: reject if < 0 or greater than the protocol maximum before sending.
- Confirm the timestamp unit (ms vs s) matches what the Signal protocol expects.
Example fix
// before
long ts = System.currentTimeMillis() * 1000; // overflow-prone, may exceed MAX_TIMESTAMP
// after
long ts = System.currentTimeMillis();
if (ts < 0 || ts > MAX_TIMESTAMP) { throw new IllegalStateException("invalid timestamp: " + ts); } Defensive patterns
Strategy: validation
Validate before calling
const ts = Date.now(); // ms
if (ts < 0 || ts > MAX_TIMESTAMP) throw new Error(`timestamp out of range: ${ts}`); Type guard
function isValidTimestamp(ts) { return typeof ts === 'number' && Number.isInteger(ts) && ts >= 0 && ts <= MAX_TIMESTAMP; } Try / catch
try { await sendMultiRecipient(msg, ts); } catch (e) { if (e.status === 400 && /Illegal timestamp/.test(e.body)) { resyncClockAndRetryWithNewTimestamp(); } } Prevention
- Sync the device clock via NTP
- Validate timestamp range client-side before every send
- Use the protocol's timestamp unit consistently (milliseconds)
When it happens
Trigger: PUT/POST to the multi-recipient message endpoint with a timestamp query/body value that is negative or greater than the server's MAX_TIMESTAMP constant.
Common situations: Clock skew or clock going backwards on the client; timestamp passed in milliseconds where the API expects the other unit (or vice versa); uninitialized/0-minus values from a bug; integer overflow when computing the timestamp.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- Invalid length
- Invalid create call link credential request
- Recipient list is empty
- Multi-recipient message contains duplicate recipient
- 400 Bad Request (UNSUPPORTED_LEVEL)
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/dba3127789c5626b.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/MessageController.java:492
@Parameter(description="If true, deliver the message only to recipients that are online when it is sent")
@QueryParam("online") boolean online,
@Parameter(description="The sender's timestamp for the envelope")
@QueryParam("ts") long timestamp,
@Parameter(description="If true, this message should cause push notifications to be sent to recipients")
@QueryParam("urgent") @DefaultValue("true") final boolean isUrgent,
@Parameter(description="If true, the message is a story; access tokens are not checked and sending to nonexistent recipients is permitted")
@QueryParam("story") boolean isStory,
@Parameter(description="The sealed-sender multi-recipient message payload as serialized by libsignal")
@NotNull SealedSenderMultiRecipientMessage multiRecipientMessage,
@Context ContainerRequestContext context) {
if (timestamp < 0 || timestamp > MAX_TIMESTAMP) {
throw new BadRequestException("Illegal timestamp");
}
if (multiRecipientMessage.getRecipients().isEmpty()) {
throw new BadRequestException("Recipient list is empty");
}
final Timer.Sample sample = Timer.start();
try {
final SendMultiRecipientMessageResponse sendMultiRecipientMessageResponse;
if (isStory) {
if (groupSendToken != null) {
// Stories require no authentication. We fail requests that provide a groupSendToken, but for historical
// reasons we allow requests to set a combined access key, even though we ignore it
throw new BadRequestException("Group send token not allowed when sending stories");
}
View on GitHub (pinned to 100ab61c82)