{"record":{"id":"3f363dca49bf1c5f","repo":"docusealco/docuseal","slug":"too-old","errorCode":null,"errorMessage":"Too old","messagePattern":"Too old","errorType":"exception","errorClass":"WebhookUrls::Signatures::TimestampError","httpStatus":null,"severity":"error","filePath":"lib/webhook_urls/signatures.rb","lineNumber":30,"sourceCode":"    module_function\n\n    def generate_secret\n      SECRET_PREFIX + Base64.strict_encode64(SecureRandom.bytes(SECRET_BYTES))\n    end\n\n    def sign(secret, body:, timestamp: Time.current.to_i)\n      \"#{timestamp}.#{OpenSSL::HMAC.hexdigest('sha256', secret, \"#{timestamp}.#{body}\")}\"\n    end\n\n    def verify(secret, body:, header:, tolerance: TOLERANCE)\n      ts, sig = header.to_s.split('.', 2)\n      ts = Integer(ts, exception: false)\n\n      raise InvalidSignatureError unless ts && sig\n\n      now = Time.current.to_i\n\n      raise TimestampError, 'Too old' if ts < now - tolerance\n      raise TimestampError, 'In future' if ts > now + tolerance\n\n      expected = OpenSSL::HMAC.hexdigest('sha256', secret, \"#{ts}.#{body}\")\n\n      raise InvalidSignatureError unless ActiveSupport::SecurityUtils.secure_compare(expected, sig)\n\n      true\n    end\n  end\nend\n","sourceCodeStart":12,"sourceCodeEnd":41,"githubUrl":"https://github.com/docusealco/docuseal/blob/004a22c1c88109c7ba0b567df011a8cb13894001/lib/webhook_urls/signatures.rb#L12-L41","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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)"],"exampleFix":"# before — verify long after delivery (queue lag) -> TimestampError 'Too old'\nWebhookUrls::Signatures.verify(secret, body: body, header: header)\n# after — verify inline in the controller, or widen tolerance for known delays\nWebhookUrls::Signatures.verify(secret, body: body, header: header, tolerance: 15 * 60)","handlingStrategy":"try-catch","validationCode":"ts = request.headers['Webhook-Signature'].to_s.split('.', 2).first.to_i\nfresh = ts >= Time.current.to_i - WebhookUrls::Signatures::TOLERANCE\nreturn head :unauthorized unless fresh # skip verify for stale timestamps","typeGuard":null,"tryCatchPattern":"begin\n  WebhookUrls::Signatures.verify(secret, body: raw_body, header: header)\nrescue WebhookUrls::Signatures::TimestampError => e\n  # e.message 'Too old' or 'In future' — replayed or clock-skewed; reject and alert, do not retry\n  head :unauthorized\nrescue WebhookUrls::Signatures::InvalidSignatureError\n  head :unauthorized\nend","preventionTips":["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"],"tags":["ruby","rails","webhook","replay-protection","timestamp","security"],"backgroundTag":"webhook-timestamp-expired","analyzedSha":"004a22c1c88109c7ba0b567df011a8cb13894001","analyzedAt":"2026-08-21T13:38:23.343Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}