Module: Philiprehberger::WebhookSignature

Defined in:
lib/philiprehberger/webhook_signature.rb,
lib/philiprehberger/webhook_signature/signer.rb,
lib/philiprehberger/webhook_signature/version.rb,
lib/philiprehberger/webhook_signature/verifier.rb

Defined Under Namespace

Classes: Error, Signer, VerificationError, Verifier

Constant Summary collapse

VERSION =
'0.4.0'

Class Method Summary collapse

Class Method Details

.sign(payload, secret:, timestamp: Time.now.to_i, algorithm: :sha256) ⇒ Hash

Convenience: sign a payload.

Parameters:

  • payload (String)

    the raw payload body

  • secret (String)

    the shared secret

  • timestamp (Integer) (defaults to: Time.now.to_i)

    Unix timestamp

  • algorithm (Symbol) (defaults to: :sha256)

    HMAC digest algorithm (:sha256 or :sha512)

Returns:

  • (Hash)

    { timestamp:, signature: }



21
22
23
# File 'lib/philiprehberger/webhook_signature.rb', line 21

def self.sign(payload, secret:, timestamp: Time.now.to_i, algorithm: :sha256)
  Signer.new(secret, algorithm: algorithm).sign(payload, timestamp: timestamp)
end

.verify(payload, timestamp:, signature:, secret: nil, secrets: nil, tolerance: 300, algorithm: :sha256) ⇒ Boolean

Convenience: verify a payload.

Parameters:

  • payload (String)

    the raw payload body

  • secret (String, nil) (defaults to: nil)

    the shared secret (single-secret form)

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

    an Array of shared secrets to support key rotation

  • timestamp (Integer)

    the timestamp

  • signature (String)

    the signature

  • tolerance (Integer, nil) (defaults to: 300)

    max age in seconds

  • algorithm (Symbol) (defaults to: :sha256)

    HMAC digest algorithm (:sha256 or :sha512)

Returns:

  • (Boolean)

Raises:

  • (ArgumentError)


35
36
37
38
39
40
41
42
# File 'lib/philiprehberger/webhook_signature.rb', line 35

def self.verify(payload, timestamp:, signature:, secret: nil, secrets: nil, tolerance: 300, algorithm: :sha256)
  raise ArgumentError, 'Provide either secret: or secrets:, not both' if !secret.nil? && !secrets.nil?
  raise ArgumentError, 'Must provide secret: or secrets:' if secret.nil? && secrets.nil?

  list = secrets.nil? ? [secret] : secrets
  Verifier.new(secrets: list, algorithm: algorithm)
          .verify(payload, timestamp: timestamp, signature: signature, tolerance: tolerance)
end