Class: HookSniff::Webhook

Inherits:
Object
  • Object
show all
Defined in:
lib/hooksniff/webhook.rb

Class Method Summary collapse

Instance Method Summary collapse

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, timestamp, payload)
  begin
    now = Integer(timestamp)
  rescue
    raise WebhookSigningError, "Invalid timestamp"
  end

  toSign = "#{msgId}.#{timestamp}.#{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.

Parameters:

  • payload (String)

    raw request body

  • headers (Hash)

    request headers containing hooksniff-id, hooksniff-timestamp, hooksniff-signature

Returns:

Raises:



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

  verify_timestamp(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.

Parameters:

  • payload (String)

    raw request body

  • headers (Hash)

    request headers

Returns:

  • (Hash, nil)

    parsed JSON hash

Raises:



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

  verify_timestamp(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