Module: AbacatePay::Webhooks

Defined in:
lib/abacate_pay/webhooks.rb,
lib/abacate_pay/webhooks/event.rb

Overview

Verification and parsing of inbound AbacatePay webhooks.

Webhook bodies are the least trusted input an integration handles: they arrive unauthenticated on a public endpoint. Every entry point here treats missing, malformed, and hostile input as an expected case and surfaces it as a typed SDK error, never as a raw parser or NoMethodError.

Defined Under Namespace

Classes: Event, PayloadError, SignatureError

Class Method Summary collapse

Class Method Details

.construct_event(payload:, signature:, secret:) ⇒ Event

Verifies a webhook signature and parses the body in one step.

This is the only entry point that cannot be used to act on an unauthenticated payload, and is what integrations should call.

Parameters:

  • payload (String)

    The raw request body

  • signature (String)

    The X-Webhook-Signature header value

  • secret (String)

    Your webhook secret/public key

Returns:

  • (Event)

    The verified, parsed event

Raises:



81
82
83
84
# File 'lib/abacate_pay/webhooks.rb', line 81

def self.construct_event(payload:, signature:, secret:)
  verify!(payload: payload, signature: signature, secret: secret)
  parse(payload)
end

.parse(payload) ⇒ Event

Parses a webhook payload into an Event object.

Prefer construct_event, which refuses to parse a body it has not authenticated first.

Parameters:

  • payload (String)

    The raw JSON request body

Returns:

  • (Event)

    The parsed event

Raises:



61
62
63
64
65
66
67
68
# File 'lib/abacate_pay/webhooks.rb', line 61

def self.parse(payload)
  data = JSON.parse(payload.to_s)
  raise PayloadError, "Expected a JSON object, got #{data.class}" unless data.is_a?(Hash)

  Event.new(data)
rescue JSON::ParserError => e
  raise PayloadError, "Malformed webhook payload: #{e.message}"
end

.valid?(payload:, signature:, secret:) ⇒ Boolean

Checks if a webhook signature is valid.

Never raises for untrusted input — a missing header, an empty secret, or a forged signature all return false.

Parameters:

  • payload (String)

    The raw request body

  • signature (String)

    The X-Webhook-Signature header value

  • secret (String)

    Your webhook secret/public key

Returns:

  • (Boolean)


46
47
48
49
50
51
# File 'lib/abacate_pay/webhooks.rb', line 46

def self.valid?(payload:, signature:, secret:)
  verify!(payload: payload, signature: signature, secret: secret)
  true
rescue SignatureError
  false
end

.verify!(payload:, signature:, secret:) ⇒ true

Verifies a webhook signature using HMAC-SHA256.

Parameters:

  • payload (String)

    The raw request body

  • signature (String)

    The X-Webhook-Signature header value

  • secret (String)

    Your webhook secret/public key

Returns:

  • (true)

    if signature is valid

Raises:



27
28
29
30
31
32
33
34
35
# File 'lib/abacate_pay/webhooks.rb', line 27

def self.verify!(payload:, signature:, secret:)
  raise SignatureError, "Missing webhook signature" if signature.nil? || signature.to_s.empty?
  raise SignatureError, "Missing webhook secret" if secret.nil? || secret.to_s.empty?

  expected = OpenSSL::HMAC.hexdigest("SHA256", secret.to_s, payload.to_s)
  raise SignatureError, "Invalid webhook signature" unless secure_compare(expected, signature.to_s)

  true
end