module DuffelAPI::WebhookEvent
Constants
- SHA_256
- SIGNATURE_REGEXP
Public Class Methods
Checks if a webhook event you received was a genuine webhook event from Duffel by checking that it was signed with your shared secret.
Assuming that you’ve kept that secret secure and only shared it with Duffel, this can give you confidence that a webhook event was genuinely sent by Duffel.
@param request_body [String] The raw body of the received request @param request_signature [String] The signature provided with the received
request, found in the `X-Duffel-Signature` request header
@param webhook_secret [String] The secret of the webhook, registered with Duffel @return [Boolean] whether the webhook signature matches
# File lib/duffel_api/webhook_event.rb, line 28 def genuine?(request_body:, request_signature:, webhook_secret:) parsed_signature = parse_signature!(request_signature) calculated_hmac = calculate_hmac( payload: request_body, secret: webhook_secret, timestamp: parsed_signature[:timestamp], ) secure_compare(calculated_hmac, parsed_signature[:v1]) rescue InvalidRequestSignatureError # If the signature doesn't even look like a valid one, then the webhook # event can't be genuine false end
Private Class Methods
Calculates the signature for a request body in the same way that the Duffel API does it
@param secret [String] @param payload [String] @param timestamp [String] @return [String]
# File lib/duffel_api/webhook_event.rb, line 53 def calculate_hmac(secret:, payload:, timestamp:) signed_payload = %(#{timestamp}.#{payload}) Base16.encode16(OpenSSL::HMAC.digest(SHA_256, secret, signed_payload)).strip.downcase end
Parses a webhook signature and extracts the ‘v1` and `timestamp` values, if available.
@param signature [String] A webhook event signature received in a request @return [Hash] @raise InvalidRequestSignatureError
when the signature isn’t valid
# File lib/duffel_api/webhook_event.rb, line 65 def parse_signature!(signature) matches = signature.match(SIGNATURE_REGEXP) if matches { v1: matches[2], timestamp: matches[1], } else raise InvalidRequestSignatureError end end
Checks if two strings are equal, performing a constant time string comparison resistant to timing attacks.
@param a [String] @param b [String] @return [Boolean] whether the two strings are equal rubocop:disable Naming/MethodParameterName
# File lib/duffel_api/webhook_event.rb, line 91 def secure_compare(a, b) # rubocop:enable Naming/MethodParameterName return false unless a.bytesize == b.bytesize OpenSSL.fixed_length_secure_compare(a, b) end