Module: Broadcast::Webhook
- Defined in:
- lib/broadcast/webhook.rb
Constant Summary collapse
- TIMESTAMP_TOLERANCE =
5 minutes
300- EMAIL_EVENTS =
Every event type a webhook endpoint can subscribe to, mirroring WebhookEndpoint::AVAILABLE_EVENT_TYPES server-side. Use these when creating an endpoint — an unknown event type is dropped silently.
%w[ email.sent email.delivered email.delivery_delayed email.complained email.bounced email.opened email.clicked email.failed ].freeze
- SUBSCRIBER_EVENTS =
%w[ subscriber.created subscriber.updated subscriber.deleted subscriber.subscribed subscriber.unsubscribed subscriber.bounced subscriber.complained ].freeze
- BROADCAST_EVENTS =
%w[ broadcast.scheduled broadcast.queueing broadcast.sending broadcast.sent broadcast.failed broadcast.partial_failure broadcast.aborted broadcast.paused ].freeze
- SEQUENCE_EVENTS =
%w[ sequence.subscriber_added sequence.subscriber_completed sequence.subscriber_moved sequence.subscriber_removed sequence.subscriber_paused sequence.subscriber_resumed sequence.subscriber_error ].freeze
- SYSTEM_EVENTS =
Delivery-machinery events, not content events.
%w[message.attempt.exhausted test.webhook].freeze
- EVENT_TYPES =
( EMAIL_EVENTS + SUBSCRIBER_EVENTS + BROADCAST_EVENTS + SEQUENCE_EVENTS + SYSTEM_EVENTS ).freeze
Class Method Summary collapse
- .compute_signature(payload, timestamp, secret) ⇒ Object
- .extract_signature(header) ⇒ Object
- .secure_compare(a, b) ⇒ Object
- .timestamp_valid?(timestamp, current_time = Time.now.to_i) ⇒ Boolean
- .verify(payload, signature_header, timestamp_header, secret:, now: nil) ⇒ Object
Class Method Details
.compute_signature(payload, timestamp, secret) ⇒ Object
60 61 62 63 64 |
# File 'lib/broadcast/webhook.rb', line 60 def compute_signature(payload, , secret) signed_content = "#{}.#{payload}" hmac = OpenSSL::HMAC.digest('SHA256', secret, signed_content) Base64.strict_encode64(hmac) end |
.extract_signature(header) ⇒ Object
70 71 72 73 74 75 76 77 |
# File 'lib/broadcast/webhook.rb', line 70 def extract_signature(header) return nil unless header&.start_with?('v1,') sig = header.delete_prefix('v1,') return nil if sig.empty? sig end |
.secure_compare(a, b) ⇒ Object
79 80 81 82 83 |
# File 'lib/broadcast/webhook.rb', line 79 def secure_compare(a, b) return false unless a.bytesize == b.bytesize OpenSSL.fixed_length_secure_compare(a, b) end |
.timestamp_valid?(timestamp, current_time = Time.now.to_i) ⇒ Boolean
66 67 68 |
# File 'lib/broadcast/webhook.rb', line 66 def (, current_time = Time.now.to_i) (current_time - ).abs <= TIMESTAMP_TOLERANCE end |
.verify(payload, signature_header, timestamp_header, secret:, now: nil) ⇒ Object
46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/broadcast/webhook.rb', line 46 def verify(payload, signature_header, , secret:, now: nil) return false if payload.nil? || signature_header.nil? || .nil? || secret.nil? = .to_i current_time = (now || Time.now).to_i return false unless (, current_time) expected = compute_signature(payload, , secret) actual = extract_signature(signature_header) return false if actual.nil? secure_compare(expected, actual) end |