Class: Api2Convert::Webhook::Verifier

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

Overview

Webhook callback verification and parsing.

Pass the raw request body (the exact string received) so signature verification is byte-exact. Verification uses HMAC-SHA256 and matches the server's signed-webhooks scheme; until signed webhooks are enabled on your account no signature is sent — use #parse then, or call #construct_event with an empty secret to skip verification.

Instance Method Summary collapse

Instance Method Details

#construct_event(payload, signature, secret) ⇒ Object

Verify the signature (when a secret is given) and return the typed event.

payload must be the raw request body. signature is the value of the signature header (X-Oc-Signature). Pass an empty secret to skip verification. Raises SignatureVerificationError when the signature is missing or does not match.



22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/api2convert/webhook/verifier.rb', line 22

def construct_event(payload, signature, secret)
  unless secret.nil? || secret == ""
    if signature.nil? || signature == ""
      raise SignatureVerificationError, "Missing webhook signature header."
    end

    expected = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, to_bytes(payload))
    unless secure_compare(expected, signature)
      raise SignatureVerificationError, "Webhook signature verification failed."
    end
  end

  parse(payload)
end

#parse(payload) ⇒ Object

Parse a callback body into a typed event WITHOUT verifying a signature. Only use this when signed webhooks are not yet enabled for your account.



39
40
41
42
43
44
45
46
47
48
# File 'lib/api2convert/webhook/verifier.rb', line 39

def parse(payload)
  begin
    decoded = JSON.parse(to_bytes(payload))
  rescue JSON::ParserError => e
    raise SignatureVerificationError, "Webhook payload is not valid JSON: #{e.message}"
  end
  raise SignatureVerificationError, "Webhook payload is not a JSON object." unless decoded.is_a?(Hash)

  Event.from_hash(decoded)
end