Class: HookSniff::Webhook
- Inherits:
-
Object
- Object
- HookSniff::Webhook
- Defined in:
- lib/hooksniff/webhook.rb
Class Method Summary collapse
Instance Method Summary collapse
-
#initialize(secret) ⇒ Webhook
constructor
A new instance of Webhook.
- #sign(msgId, timestamp, payload) ⇒ Object
-
#verify(payload, headers) ⇒ WebhookEvent
Verify and parse a webhook payload.
-
#verify_raw(payload, headers) ⇒ Hash?
Verify and return raw payload without parsing.
Constructor Details
#initialize(secret) ⇒ Webhook
Returns a new instance of Webhook.
17 18 19 20 21 22 23 |
# File 'lib/hooksniff/webhook.rb', line 17 def initialize(secret) if secret.start_with?(SECRET_PREFIX) secret = secret[SECRET_PREFIX.length..-1] end @secret = Base64.decode64(secret) end |
Class Method Details
.new_using_raw_bytes(secret) ⇒ Object
13 14 15 |
# File 'lib/hooksniff/webhook.rb', line 13 def self.new_using_raw_bytes(secret) self.new(secret.pack("C*").force_encoding("UTF-8")) end |
Instance Method Details
#sign(msgId, timestamp, payload) ⇒ Object
106 107 108 109 110 111 112 113 114 115 116 |
# File 'lib/hooksniff/webhook.rb', line 106 def sign(msgId, , payload) begin now = Integer() rescue raise WebhookSigningError, "Invalid timestamp" end toSign = "#{msgId}.#{}.#{payload}" signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest.new("sha256"), @secret, toSign)).strip return "v1,#{signature}" end |
#verify(payload, headers) ⇒ WebhookEvent
Verify and parse a webhook payload.
Verifies the HMAC-SHA256 signature, then parses the payload into a typed WebhookEvent with event, data, and timestamp.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/hooksniff/webhook.rb', line 34 def verify(payload, headers) msgId = headers["hooksniff-id"] msgSignature = headers["hooksniff-signature"] msgTimestamp = headers["hooksniff-timestamp"] if !msgSignature || !msgId || !msgTimestamp msgId = headers["webhook-id"] msgSignature = headers["webhook-signature"] msgTimestamp = headers["webhook-timestamp"] if !msgSignature || !msgId || !msgTimestamp raise WebhookVerificationError, "Missing required headers" end end (msgTimestamp) _, signature = sign(msgId, msgTimestamp, payload).split(",", 2) passedSignatures = msgSignature.split(" ") passedSignatures.each do |versionedSignature| version, expectedSignature = versionedSignature.split(",", 2) if version != "v1" next end if ::HookSniff::secure_compare(signature, expectedSignature) return parse_payload(payload) end end raise WebhookVerificationError, "No matching signature found" end |
#verify_raw(payload, headers) ⇒ Hash?
Verify and return raw payload without parsing. Use this when you need the raw hash instead of a typed event.
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 104 |
# File 'lib/hooksniff/webhook.rb', line 73 def verify_raw(payload, headers) msgId = headers["hooksniff-id"] msgSignature = headers["hooksniff-signature"] msgTimestamp = headers["hooksniff-timestamp"] if !msgSignature || !msgId || !msgTimestamp msgId = headers["webhook-id"] msgSignature = headers["webhook-signature"] msgTimestamp = headers["webhook-timestamp"] if !msgSignature || !msgId || !msgTimestamp raise WebhookVerificationError, "Missing required headers" end end (msgTimestamp) _, signature = sign(msgId, msgTimestamp, payload).split(",", 2) passedSignatures = msgSignature.split(" ") passedSignatures.each do |versionedSignature| version, expectedSignature = versionedSignature.split(",", 2) if version != "v1" next end if ::HookSniff::secure_compare(signature, expectedSignature) return nil if payload.empty? return JSON.parse(payload, symbolize_names: true) end end raise WebhookVerificationError, "No matching signature found" end |