Module: Sendly::Webhooks
- Defined in:
- lib/sendly/webhooks.rb
Overview
Webhook utilities for verifying and parsing Sendly webhook events.
Defined Under Namespace
Modules: ListHealthEventSource
Constant Summary collapse
- SIGNATURE_TOLERANCE_SECONDS =
300- EVENT_MESSAGE_QUEUED =
Webhook event type string constants. Use these when subscribing instead of string literals so you catch typos at load time.
"message.queued"- EVENT_MESSAGE_SENT =
"message.sent"- EVENT_MESSAGE_DELIVERED =
"message.delivered"- EVENT_MESSAGE_FAILED =
"message.failed"- EVENT_MESSAGE_BOUNCED =
"message.bounced"- EVENT_MESSAGE_RETRYING =
"message.retrying"- EVENT_MESSAGE_RECEIVED =
"message.received"- EVENT_MESSAGE_OPT_OUT =
"message.opt_out"- EVENT_MESSAGE_OPT_IN =
"message.opt_in"- EVENT_VERIFICATION_CREATED =
"verification.created"- EVENT_VERIFICATION_DELIVERED =
"verification.delivered"- EVENT_VERIFICATION_VERIFIED =
"verification.verified"- EVENT_VERIFICATION_EXPIRED =
"verification.expired"- EVENT_VERIFICATION_FAILED =
"verification.failed"- EVENT_VERIFICATION_RESENT =
"verification.resent"- EVENT_VERIFICATION_DELIVERY_FAILED =
"verification.delivery_failed"- EVENT_CONTACT_AUTO_FLAGGED =
"contact.auto_flagged"- EVENT_CONTACT_MARKED_VALID =
"contact.marked_valid"- EVENT_CONTACTS_LOOKUP_COMPLETED =
"contacts.lookup_completed"- EVENT_CONTACTS_BULK_MARKED_VALID =
"contacts.bulk_marked_valid"- EVENT_BRAND_VERIFIED =
"brand.verified"- EVENT_BRAND_FAILED =
"brand.failed"- EVENT_CAMPAIGN_APPROVED =
"campaign.approved"- EVENT_CAMPAIGN_REJECTED =
"campaign.rejected"- EVENT_CAMPAIGN_SUSPENDED =
"campaign.suspended"- EVENT_ASSIGNMENT_CONFIRMED =
"assignment.confirmed"- EVENT_ASSIGNMENT_FAILED =
"assignment.failed"- EVENT_PORT_COMPLETED =
"port.completed"- EVENT_PORT_OUT_REQUESTED =
"port_out.requested"- EVENT_PORT_OUT_COMPLETED =
"port_out.completed"- EVENT_PORT_OUT_REJECTED =
"port_out.rejected"- EVENT_PORT_OUT_CANCELLED =
"port_out.cancelled"- EVENT_NUMBER_ACTIVATED =
"number.activated"- EVENT_NUMBER_FAILED =
"number.failed"- EVENT_NUMBER_REQUIREMENTS_REQUIRED =
"number.requirements_required"- EVENT_NUMBER_RELEASED =
"number.released"
Class Method Summary collapse
-
.generate_signature(payload, secret, timestamp: nil) ⇒ String
Generate a webhook signature for testing purposes.
-
.parse_event(payload, signature, secret, timestamp: nil) ⇒ WebhookEvent
Parse and validate a webhook event.
-
.verify_signature(payload, signature, secret, timestamp: nil) ⇒ Boolean
Verify webhook signature from Sendly.
Class Method Details
.generate_signature(payload, secret, timestamp: nil) ⇒ String
Generate a webhook signature for testing purposes.
142 143 144 145 |
# File 'lib/sendly/webhooks.rb', line 142 def generate_signature(payload, secret, timestamp: nil) signed_payload = ? "#{}.#{payload}" : payload 'sha256=' + OpenSSL::HMAC.hexdigest('SHA256', secret, signed_payload) end |
.parse_event(payload, signature, secret, timestamp: nil) ⇒ WebhookEvent
Parse and validate a webhook event.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/sendly/webhooks.rb', line 120 def parse_event(payload, signature, secret, timestamp: nil) unless verify_signature(payload, signature, secret, timestamp: ) raise WebhookSignatureError, 'Invalid webhook signature' end data = JSON.parse(payload, symbolize_names: true) unless data[:id] && data[:type] && data[:data] raise WebhookSignatureError, 'Invalid event structure' end WebhookEvent.new(data) rescue JSON::ParserError => e raise WebhookSignatureError, "Failed to parse webhook payload: #{e.}" end |
.verify_signature(payload, signature, secret, timestamp: nil) ⇒ Boolean
Verify webhook signature from Sendly.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/sendly/webhooks.rb', line 95 def verify_signature(payload, signature, secret, timestamp: nil) return false if payload.nil? || payload.empty? return false if signature.nil? || signature.empty? return false if secret.nil? || secret.empty? if signed_payload = "#{}.#{payload}" return false if (Time.now.to_i - .to_i).abs > SIGNATURE_TOLERANCE_SECONDS else signed_payload = payload end expected = 'sha256=' + OpenSSL::HMAC.hexdigest('SHA256', secret, signed_payload) secure_compare(expected, signature) end |