Module: WorkOS::Webhooks
- Defined in:
- lib/workos/webhooks.rb
Overview
The Webhooks module provides convenience methods for working with the WorkOS webhooks. You’ll need to extract the signature header and payload from the webhook request sig_header = request.headers payload = request.body.read
The secret is the Webhook Secret from your WorkOS Dashboard The tolerance is for the timestamp validation
Constant Summary collapse
- DEFAULT_TOLERANCE =
180
Class Method Summary collapse
-
.compute_signature(timestamp:, payload:, secret:) ⇒ Object
Computes expected signature rubocop:disable Layout/LineLength.
-
.construct_event(payload:, sig_header:, secret:, tolerance: DEFAULT_TOLERANCE) ⇒ WorkOS::Webhook
Initializes an Event object from a JSON payload rubocop:disable Layout/LineLength.
-
.get_timestamp_and_signature_hash(sig_header:) ⇒ Object
Extracts timestamp and signature hash from WorkOS-Signature header.
-
.secure_compare(str_a:, str_b:) ⇒ Object
Constant time string comparison to prevent timing attacks Code borrowed from ActiveSupport.
-
.verify_header(payload:, sig_header:, secret:, tolerance: DEFAULT_TOLERANCE) ⇒ Object
Verifies WorkOS-Signature header from request rubocop:disable Layout/LineLength.
Class Method Details
.compute_signature(timestamp:, payload:, secret:) ⇒ Object
Computes expected signature rubocop:disable Layout/LineLength
rubocop:enable Layout/LineLength
153 154 155 156 157 158 159 160 161 |
# File 'lib/workos/webhooks.rb', line 153 def compute_signature( timestamp:, payload:, secret: ) unhashed_string = "#{timestamp}.#{payload}" digest = OpenSSL::Digest.new('sha256') OpenSSL::HMAC.hexdigest(digest, secret, unhashed_string) end |
.construct_event(payload:, sig_header:, secret:, tolerance: DEFAULT_TOLERANCE) ⇒ WorkOS::Webhook
Initializes an Event object from a JSON payload rubocop:disable Layout/LineLength
rubocop:enable Layout/LineLength
37 38 39 40 41 42 43 44 45 |
# File 'lib/workos/webhooks.rb', line 37 def construct_event( payload:, sig_header:, secret:, tolerance: DEFAULT_TOLERANCE ) verify_header(payload: payload, sig_header: sig_header, secret: secret, tolerance: tolerance) WorkOS::Webhook.new(payload) end |
.get_timestamp_and_signature_hash(sig_header:) ⇒ Object
Extracts timestamp and signature hash from WorkOS-Signature header
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/workos/webhooks.rb', line 118 def ( sig_header: ) , signature_hash = sig_header.split(', ') if .nil? || signature_hash.nil? raise WorkOS::SignatureVerificationError.new( message: 'Unable to extract timestamp and signature hash from header', ) end = .sub('t=', '') signature_hash = signature_hash.sub('v1=', '') [, signature_hash] end |
.secure_compare(str_a:, str_b:) ⇒ Object
Constant time string comparison to prevent timing attacks Code borrowed from ActiveSupport
165 166 167 168 169 170 171 172 173 174 175 176 177 |
# File 'lib/workos/webhooks.rb', line 165 def secure_compare( str_a:, str_b: ) return false unless str_a.bytesize == str_b.bytesize l = str_a.unpack("C#{str_a.bytesize}") res = 0 str_b.each_byte { |byte| res |= byte ^ l.shift } res.zero? end |
.verify_header(payload:, sig_header:, secret:, tolerance: DEFAULT_TOLERANCE) ⇒ Object
Verifies WorkOS-Signature header from request rubocop:disable Layout/LineLength
rubocop:enable Layout/LineLength rubocop:disable Metrics/AbcSize
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/workos/webhooks.rb', line 67 def verify_header( payload:, sig_header:, secret:, tolerance: DEFAULT_TOLERANCE ) begin , signature_hash = (sig_header: sig_header) rescue StandardError raise WorkOS::SignatureVerificationError.new( message: 'Unable to extract timestamp and signature hash from header', ) end if signature_hash.empty? raise WorkOS::SignatureVerificationError.new( message: 'No signature hash found with expected scheme v1', ) end = Time.at(.to_i / 1000) if < Time.now - tolerance raise WorkOS::SignatureVerificationError.new( message: 'Timestamp outside the tolerance zone', ) end expected_sig = compute_signature(timestamp: , payload: payload, secret: secret) unless secure_compare(str_a: expected_sig, str_b: signature_hash) raise WorkOS::SignatureVerificationError.new( message: 'Signature hash does not match the expected signature hash for payload', ) end true end |