docusealco/docuseal · error · WebhookUrls::Signatures::TimestampError

Too old

Error message

Too old

What it means

Raised by WebhookUrls::Signatures.verify (TimestampError, message 'Too old') when the parsed timestamp is older than now - tolerance (default TOLERANCE = 300 seconds). This is the replay protection: a validly signed payload stops being accepted five minutes after signing.

Source

Thrown at lib/webhook_urls/signatures.rb:30

    module_function

    def generate_secret
      SECRET_PREFIX + Base64.strict_encode64(SecureRandom.bytes(SECRET_BYTES))
    end

    def sign(secret, body:, timestamp: Time.current.to_i)
      "#{timestamp}.#{OpenSSL::HMAC.hexdigest('sha256', secret, "#{timestamp}.#{body}")}"
    end

    def verify(secret, body:, header:, tolerance: TOLERANCE)
      ts, sig = header.to_s.split('.', 2)
      ts = Integer(ts, exception: false)

      raise InvalidSignatureError unless ts && sig

      now = Time.current.to_i

      raise TimestampError, 'Too old' if ts < now - tolerance
      raise TimestampError, 'In future' if ts > now + tolerance

      expected = OpenSSL::HMAC.hexdigest('sha256', secret, "#{ts}.#{body}")

      raise InvalidSignatureError unless ActiveSupport::SecurityUtils.secure_compare(expected, sig)

      true
    end
  end
end

View on GitHub (pinned to 004a22c1c8)

Solutions

  1. Verify the signature immediately on receipt, before enqueuing or doing slow work
  2. As the sender, sign at the moment of delivery and re-sign on each retry
  3. Keep clocks NTP-synced on both sender and receiver
  4. If legitimate processing delays exist, pass a larger window: verify(secret, body:, header:, tolerance: 15 * 60)

Example fix

# before — verify long after delivery (queue lag) -> TimestampError 'Too old'
WebhookUrls::Signatures.verify(secret, body: body, header: header)
# after — verify inline in the controller, or widen tolerance for known delays
WebhookUrls::Signatures.verify(secret, body: body, header: header, tolerance: 15 * 60)
Defensive patterns

Strategy: try-catch

Validate before calling

ts = request.headers['Webhook-Signature'].to_s.split('.', 2).first.to_i
fresh = ts >= Time.current.to_i - WebhookUrls::Signatures::TOLERANCE
return head :unauthorized unless fresh # skip verify for stale timestamps

Try / catch

begin
  WebhookUrls::Signatures.verify(secret, body: raw_body, header: header)
rescue WebhookUrls::Signatures::TimestampError => e
  # e.message 'Too old' or 'In future' — replayed or clock-skewed; reject and alert, do not retry
  head :unauthorized
rescue WebhookUrls::Signatures::InvalidSignatureError
  head :unauthorized
end

Prevention

When it happens

Trigger: Calling verify more than 5 minutes after the payload was signed — delayed webhook delivery, queued processing that verifies late, a replayed request, or a receiver clock running ahead of the signer (skew makes valid timestamps look old).

Common situations: Webhooks sit in a background queue or slow middleware and are verified after the window closes; a sender precomputes signatures then delays dispatch (e.g. batch sends, retries hours later); server clocks without NTP drift apart; test suites replay recorded fixtures with old timestamps.

Related errors


AI-assisted analysis of docusealco/docuseal@004a22c1c8 (2026-08-21). Data as JSON: /api/errors/3f363dca49bf1c5f. Report an issue: GitHub.