Module: Polar::Webhooks
- Defined in:
- lib/polar/webhooks.rb
Defined Under Namespace
Classes: Event
Class Method Summary collapse
-
.compute_signature(payload, timestamp, secret) ⇒ String
Compute expected signature for payload.
-
.event_type?(payload, event_type) ⇒ Boolean
Check if event is a specific type.
-
.extract_signature(headers) ⇒ String
Extract signature from webhook headers.
-
.extract_timestamp(headers) ⇒ String
Extract timestamp from webhook headers.
-
.parse_event_type(payload) ⇒ String
Parse webhook event type from payload.
-
.secure_compare(a, b) ⇒ Boolean
Secure string comparison to prevent timing attacks.
-
.validate_event(payload, headers, secret) ⇒ Hash
Validate webhook event signature.
-
.verify_signature(payload, timestamp, signature, secret) ⇒ Boolean
Verify webhook signature without parsing payload.
-
.verify_timestamp(timestamp, tolerance: 300) ⇒ Object
Verify timestamp is within tolerance.
Class Method Details
.compute_signature(payload, timestamp, secret) ⇒ String
Compute expected signature for payload
89 90 91 92 93 94 95 96 97 |
# File 'lib/polar/webhooks.rb', line 89 def compute_signature(payload, , secret) signed_payload = "#{}.#{payload}" OpenSSL::HMAC.hexdigest( OpenSSL::Digest.new('sha256'), secret, signed_payload ) end |
.event_type?(payload, event_type) ⇒ Boolean
Check if event is a specific type
125 126 127 |
# File 'lib/polar/webhooks.rb', line 125 def event_type?(payload, event_type) parse_event_type(payload) == event_type end |
.extract_signature(headers) ⇒ String
Extract signature from webhook headers
55 56 57 58 59 60 61 62 |
# File 'lib/polar/webhooks.rb', line 55 def extract_signature(headers) signature = headers['polar-signature'] || headers['Polar-Signature'] raise WebhookVerificationError, 'Missing polar-signature header' unless signature # Remove the signature prefix if present (e.g., "v1=signature") signature.split('=').last end |
.extract_timestamp(headers) ⇒ String
Extract timestamp from webhook headers
44 45 46 47 48 49 50 |
# File 'lib/polar/webhooks.rb', line 44 def (headers) = headers['polar-timestamp'] || headers['Polar-Timestamp'] raise WebhookVerificationError, 'Missing polar-timestamp header' unless end |
.parse_event_type(payload) ⇒ String
Parse webhook event type from payload
114 115 116 117 118 119 |
# File 'lib/polar/webhooks.rb', line 114 def parse_event_type(payload) data = payload.is_a?(String) ? JSON.parse(payload) : payload data['type'] || data['event_type'] rescue JSON::ParserError nil end |
.secure_compare(a, b) ⇒ Boolean
Secure string comparison to prevent timing attacks
103 104 105 106 107 108 109 |
# File 'lib/polar/webhooks.rb', line 103 def secure_compare(a, b) return false unless a.length == b.length result = 0 a.bytes.zip(b.bytes) { |x, y| result |= x ^ y } result == 0 end |
.validate_event(payload, headers, secret) ⇒ Hash
Validate webhook event signature
15 16 17 18 19 20 21 22 23 24 25 |
# File 'lib/polar/webhooks.rb', line 15 def validate_event(payload, headers, secret) = (headers) signature = extract_signature(headers) () verify_signature(payload, , signature, secret) JSON.parse(payload) rescue JSON::ParserError => e raise WebhookError, "Invalid JSON payload: #{e.}" end |
.verify_signature(payload, timestamp, signature, secret) ⇒ Boolean
Verify webhook signature without parsing payload
33 34 35 36 37 38 39 |
# File 'lib/polar/webhooks.rb', line 33 def verify_signature(payload, , signature, secret) expected_signature = compute_signature(payload, , secret) raise WebhookVerificationError, 'Invalid webhook signature' unless secure_compare(signature, expected_signature) true end |
.verify_timestamp(timestamp, tolerance: 300) ⇒ Object
Verify timestamp is within tolerance
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
# File 'lib/polar/webhooks.rb', line 67 def (, tolerance: 300) begin webhook_time = Time.at(.to_i) rescue ArgumentError raise WebhookVerificationError, 'Invalid timestamp format' end current_time = Time.now age = current_time - webhook_time raise WebhookVerificationError, "Webhook timestamp too old (#{age.to_i}s > #{tolerance}s)" if age > tolerance raise WebhookVerificationError, 'Webhook timestamp too far in future' if age < -tolerance true end |