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

  1. Check the client's system clock and sync via NTP before sending.
  2. Clamp/validate the timestamp client-side: reject if < 0 or greater than the protocol maximum before sending.
  3. 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

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


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)