Class: Mercadopago::Webhook::Validator

Inherits:
Object
  • Object
show all
Defined in:
lib/mercadopago/webhook/validator.rb

Overview

Stateless utility that validates the signature of a MercadoPago webhook.

On failure Validator.validate raises InvalidWebhookSignatureError; on success it returns nil. The comparison is performed in constant time via OpenSSL.fixed_length_secure_compare to mitigate timing attacks.

**QR Code notifications are not signed** by MercadoPago — do not call this validator for those events; they will always fail signature verification.

Class Method Summary collapse

Class Method Details

.validate(x_signature, x_request_id, data_id, secret, tolerance_seconds: nil, supported_versions: nil, now: nil) ⇒ void

This method returns an undefined value.

Validates the signature of a MercadoPago webhook notification.

Parameters:

  • x_signature (String, nil)

    raw value of the x-signature request header

  • x_request_id (String, nil)

    value of the x-request-id request header. May be nil or blank; in that case the request-id: pair is omitted from the manifest before computing the HMAC

  • data_id (String, nil)

    value of the data.id query parameter. May be nil or blank; in that case the id: pair is omitted. Lowercased before HMAC

  • secret (String)

    secret signature configured in Tus Integraciones (HMAC key)

  • tolerance_seconds (Integer, nil) (defaults to: nil)

    optional maximum allowed drift in seconds between the header timestamp and the current clock. Omit to skip the check

  • supported_versions (Array<String>, nil) (defaults to: nil)

    optional ordered list of signature versions to accept. Defaults to %w. The first version found in the header is used

  • now (Proc, nil) (defaults to: nil)

    optional callable returning the current time in milliseconds since Unix epoch. Intended for tests

Raises:

  • (InvalidWebhookSignatureError)

    when the signature is missing, malformed, or does not match the expected HMAC

  • (ArgumentError)

    when secret is nil or empty



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/mercadopago/webhook/validator.rb', line 100

def self.validate(x_signature, x_request_id, data_id, secret,
                  tolerance_seconds: nil, supported_versions: nil, now: nil)
  raise ArgumentError, 'secret must not be empty' if secret.nil? || secret.empty?

  x_signature = normalize(x_signature)
  x_request_id = normalize(x_request_id)
  data_id = normalize(data_id)
  versions = resolve_versions(supported_versions)
  now_proc = now || -> { (Time.now.to_f * 1000).to_i }

  ts, received_hash = parse_and_validate_header(x_signature, x_request_id, versions)
  verify_signature!(data_id, x_request_id, ts, secret, received_hash)
  check_tolerance!(ts, x_request_id, tolerance_seconds, now_proc)
  nil
end