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
- Verify the signature immediately on receipt, before enqueuing or doing slow work
- As the sender, sign at the moment of delivery and re-sign on each retry
- Keep clocks NTP-synced on both sender and receiver
- 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
- Verify signatures synchronously in the HTTP handler, before any queueing
- Re-sign payloads on delivery retries rather than reusing an old signature
- Monitor clock sync (NTP/chrony) on receivers; skew in either direction breaks the ±5-minute window
- Record the header timestamp when rejecting so you can quantify queue lag
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.