Module: BulletTrain::OutgoingWebhooks::Signature

Defined in:
lib/bullet_train/outgoing_webhooks/signature.rb

Overview

Provides methods for webhook signatures. This module also serves as an example that can be used by receiving applications to verify webhook authenticity.

Class Method Summary collapse

Class Method Details

.generate(payload, secret) ⇒ Object

Algorithm to generate the signature.



56
57
58
59
60
61
62
63
64
65
# File 'lib/bullet_train/outgoing_webhooks/signature.rb', line 56

def self.generate(payload, secret)
  timestamp = Time.now.to_i.to_s
  signature_payload = "#{timestamp}.#{payload.to_json}"
  signature = OpenSSL::HMAC.hexdigest("SHA256", secret, signature_payload)

  {
    signature: signature,
    timestamp: timestamp
  }
end

.verify(payload, signature, timestamp, secret) ⇒ Boolean

Verifies the authenticity of a webhook request.

Parameters:

  • payload (String)

    The raw request body as a string.

  • timestamp (String)

    The timestamp from the Timestamp request header.

  • signature (String)

    The signature from the Signature request header.

  • secret (String)

    The webhook secret attached to the endpoint the event comes from.

Returns:

  • (Boolean)

    True if the signature is valid, false otherwise.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/bullet_train/outgoing_webhooks/signature.rb', line 14

def self.verify(payload, signature, timestamp, secret)
  return false if payload.blank? || signature.blank? || timestamp.blank? || secret.blank?

  tolerance = Rails.configuration.outgoing_webhooks[:event_verification_tolerance_seconds]
  # Check if the timestamp is too old
  timestamp_int = timestamp.to_i
  now = Time.now.to_i

  if (now - timestamp_int).abs > tolerance
    return false # Webhook is too old or timestamp is from the future
  end

  # Compute the expected signature
  signature_payload = "#{timestamp}.#{payload}"
  expected_signature = OpenSSL::HMAC.hexdigest("SHA256", secret, signature_payload)

  # Compare signatures using constant-time comparison to prevent timing attacks
  ActiveSupport::SecurityUtils.secure_compare(expected_signature, signature)
end

.verify_request(request, secret) ⇒ Boolean

A Rails controller helper example to verify webhook requests.

Parameters:

  • request (ActionDispatch::Request)

    The Rails request object.

  • secret (String)

    The webhook secret shared with the sender.

Returns:

  • (Boolean)

    True if the signature is valid, false otherwise.



39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/bullet_train/outgoing_webhooks/signature.rb', line 39

def self.verify_request(request, secret)
  return false if request.blank? || secret.blank?

  webhook_headers_namespace = Rails.configuration.outgoing_webhooks[:webhook_headers_namespace]
  signature = request.headers["#{webhook_headers_namespace}-Signature"]
  timestamp = request.headers["#{webhook_headers_namespace}-Timestamp"]
  payload = request.raw_post

  return false if signature.blank? || timestamp.blank?

  verify(payload, signature, timestamp, secret)
end