Module: TrueTrial::Webhook
- Defined in:
- lib/truetrial/webhook.rb
Overview
Utilities for verifying and parsing incoming TrueTrial webhooks.
Webhook payloads are signed with HMAC SHA-256. The signature is delivered in the X-TrueTrial-Signature header and is computed from the raw JSON body using the webhook secret.
Class Method Summary collapse
-
.compute_signature(payload, secret) ⇒ String
Computes the HMAC SHA-256 hex digest for a payload.
-
.construct_event(payload, signature, secret, tolerance: nil, timestamp: nil) ⇒ Hash
Verifies and parses a webhook payload into a hash.
-
.secure_compare(a, b) ⇒ Boolean
Performs a constant-time string comparison to prevent timing attacks.
-
.verify?(payload, signature, secret, tolerance: nil, timestamp: nil) ⇒ Boolean
Verifies that a webhook payload matches its signature.
Class Method Details
.compute_signature(payload, secret) ⇒ String
Computes the HMAC SHA-256 hex digest for a payload.
60 61 62 |
# File 'lib/truetrial/webhook.rb', line 60 def compute_signature(payload, secret) OpenSSL::HMAC.hexdigest("SHA256", secret, payload) end |
.construct_event(payload, signature, secret, tolerance: nil, timestamp: nil) ⇒ Hash
Verifies and parses a webhook payload into a hash.
47 48 49 50 51 52 53 |
# File 'lib/truetrial/webhook.rb', line 47 def construct_event(payload, signature, secret, tolerance: nil, timestamp: nil) unless verify?(payload, signature, secret, tolerance: tolerance, timestamp: ) raise Error.new("Invalid webhook signature") end JSON.parse(payload) end |
.secure_compare(a, b) ⇒ Boolean
Performs a constant-time string comparison to prevent timing attacks.
69 70 71 72 73 74 75 76 77 78 79 80 |
# File 'lib/truetrial/webhook.rb', line 69 def secure_compare(a, b) return false unless a.bytesize == b.bytesize OpenSSL.fixed_length_secure_compare(a, b) rescue NoMethodError # Fallback for older OpenSSL versions l = a.unpack("C*") r = b.unpack("C*") result = 0 l.zip(r) { |x, y| result |= x ^ y } result.zero? end |
.verify?(payload, signature, secret, tolerance: nil, timestamp: nil) ⇒ Boolean
Verifies that a webhook payload matches its signature.
24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/truetrial/webhook.rb', line 24 def verify?(payload, signature, secret, tolerance: nil, timestamp: nil) expected = compute_signature(payload, secret) valid = secure_compare(expected, signature) if valid && tolerance && event_time = Time.parse() valid = (Time.now - event_time).abs <= tolerance end valid rescue ArgumentError, TypeError false end |