Module: Finlight::WebhookService
- Defined in:
- lib/finlight/webhook_service.rb
Overview
Securely receives and verifies webhook events from finlight: HMAC-SHA256 signature verification with replay attack protection.
Constant Summary collapse
- SIGNATURE_PREFIX =
"sha256="- REPLAY_TOLERANCE_SECONDS =
5 * 60
Class Method Summary collapse
- .compute_signature(payload, secret) ⇒ Object
-
.construct_event(raw_body, signature, endpoint_secret, timestamp = nil) ⇒ Article
Constructs and verifies a webhook event from raw request data.
- .parse_payload(raw_body) ⇒ Object
- .secure_compare(left, right) ⇒ Object
- .verify_timestamp(timestamp) ⇒ Object
Class Method Details
.compute_signature(payload, secret) ⇒ Object
47 48 49 |
# File 'lib/finlight/webhook_service.rb', line 47 def compute_signature(payload, secret) OpenSSL::HMAC.hexdigest("SHA256", secret, payload) end |
.construct_event(raw_body, signature, endpoint_secret, timestamp = nil) ⇒ Article
Constructs and verifies a webhook event from raw request data.
34 35 36 37 38 39 40 41 42 43 44 45 |
# File 'lib/finlight/webhook_service.rb', line 34 def construct_event(raw_body, signature, endpoint_secret, = nil) = nil if .to_s.empty? normalized = signature.to_s.delete_prefix(SIGNATURE_PREFIX) = ? "#{}.#{raw_body}" : raw_body expected = compute_signature(, endpoint_secret) raise WebhookVerificationError, "Invalid webhook signature" unless secure_compare(normalized, expected) () if parse_payload(raw_body) end |
.parse_payload(raw_body) ⇒ Object
63 64 65 66 67 68 69 70 71 72 73 74 75 |
# File 'lib/finlight/webhook_service.rb', line 63 def parse_payload(raw_body) data = begin JSON.parse(raw_body) rescue JSON::ParserError raise WebhookVerificationError, "Invalid JSON payload" end begin Article.from_h(data) rescue StandardError => e raise WebhookVerificationError, "Invalid article data: #{e.}" end end |
.secure_compare(left, right) ⇒ Object
77 78 79 80 81 |
# File 'lib/finlight/webhook_service.rb', line 77 def secure_compare(left, right) return false unless left.bytesize == right.bytesize OpenSSL.fixed_length_secure_compare(left, right) end |
.verify_timestamp(timestamp) ⇒ Object
51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/finlight/webhook_service.rb', line 51 def () begin webhook_time = FlexTime.parse() rescue Error raise WebhookVerificationError, "Invalid timestamp format" end return unless (Time.now.utc - webhook_time).abs > REPLAY_TOLERANCE_SECONDS raise WebhookVerificationError, "Webhook timestamp outside allowed tolerance" end |