vortex-ruby-sdk 1.8.4 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 677efb240644fe95c61d07a325952a6d2f8c6851eb14d59d17178d2c74881629
4
- data.tar.gz: 83ad91fa108419346c2b6a707b62316573fdc5137d213a141840a599eca02dbe
3
+ metadata.gz: b8665551bcdf4cf5b7ab05fc5b2dd880b9e1166dd4f544acceb864365e86c8db
4
+ data.tar.gz: 82d89462684d03e6b26769f20b8a44ba3f4fe71cf729ce78f54268f070fa36df
5
5
  SHA512:
6
- metadata.gz: 324e85fc0660f7b952932a163ad10df4ae17f8f81dafd7c8eb6d8de70e771146474c0cc147d7643ae689f9629eafba0b9884c6f022b3091fb4147a3a1dc2f1eb
7
- data.tar.gz: 2d780b17c5697ffcb0532bfcabce7756bd80a672b74417b5194e0c3e9089de31c0bc8c03977b4a0fdce0e416af1bb2a85868b2aa6050d2bf6a9fe9fbf7aa45e5
6
+ metadata.gz: 32a6d0a546f91c5165f43af6ac0ed995386b92d267006eb6793b865676fe0bcfe6631e748294ecf589253d7621df492aff7b442881266f7906dc6833b78be63e
7
+ data.tar.gz: 39253da9f792aacc194e619eb4ab00104d15ce7901ca5345a82e1681f3aa2d0e0bb268c6b832fad3537736641378f57fe3ee2ae31a5af8b2157ea5a8a2b02c17
data/README.md CHANGED
@@ -262,6 +262,50 @@ To install this gem onto your local machine, run `bundle exec rake install`.
262
262
 
263
263
  ## Contributing
264
264
 
265
+ ## Webhooks
266
+
267
+ The SDK provides built-in support for verifying and parsing incoming webhook events from Vortex.
268
+
269
+ ```ruby
270
+ require 'vortex'
271
+
272
+ webhooks = Vortex::Webhooks.new(secret: ENV['VORTEX_WEBHOOK_SECRET'])
273
+
274
+ # In your HTTP handler (Rails example):
275
+ class WebhooksController < ApplicationController
276
+ skip_before_action :verify_authenticity_token
277
+
278
+ def create
279
+ payload = request.body.read
280
+ signature = request.headers['X-Vortex-Signature']
281
+
282
+ begin
283
+ event = webhooks.construct_event(payload, signature)
284
+ rescue Vortex::WebhookSignatureError
285
+ head :bad_request
286
+ return
287
+ end
288
+
289
+ case event
290
+ when Vortex::WebhookEvent
291
+ Rails.logger.info "Webhook event: #{event.type}"
292
+ when Vortex::AnalyticsEvent
293
+ Rails.logger.info "Analytics event: #{event.name}"
294
+ end
295
+
296
+ head :ok
297
+ end
298
+ end
299
+ ```
300
+
301
+ ### Event Type Constants
302
+
303
+ ```ruby
304
+ if event.type == Vortex::WebhookEventTypes::INVITATION_ACCEPTED
305
+ # Handle invitation accepted
306
+ end
307
+ ```
308
+
265
309
  Bug reports and pull requests are welcome on GitHub at https://github.com/vortexsoftware/vortex-ruby-sdk.
266
310
 
267
311
  ## License
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vortex
4
- VERSION = '1.8.4'
4
+ VERSION = '1.9.0'
5
5
  end
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Vortex
4
+ # Webhook event type constants
5
+ module WebhookEventTypes
6
+ # Invitation Lifecycle
7
+ INVITATION_CREATED = 'invitation.created'
8
+ INVITATION_ACCEPTED = 'invitation.accepted'
9
+ INVITATION_DEACTIVATED = 'invitation.deactivated'
10
+ INVITATION_EMAIL_DELIVERED = 'invitation.email.delivered'
11
+ INVITATION_EMAIL_BOUNCED = 'invitation.email.bounced'
12
+ INVITATION_EMAIL_OPENED = 'invitation.email.opened'
13
+ INVITATION_LINK_CLICKED = 'invitation.link.clicked'
14
+ INVITATION_REMINDER_SENT = 'invitation.reminder.sent'
15
+
16
+ # Deployment Lifecycle
17
+ DEPLOYMENT_CREATED = 'deployment.created'
18
+ DEPLOYMENT_DEACTIVATED = 'deployment.deactivated'
19
+
20
+ # A/B Testing
21
+ ABTEST_STARTED = 'abtest.started'
22
+ ABTEST_WINNER_DECLARED = 'abtest.winner_declared'
23
+
24
+ # Member/Group
25
+ MEMBER_CREATED = 'member.created'
26
+ GROUP_MEMBER_ADDED = 'group.member.added'
27
+
28
+ # Email
29
+ EMAIL_COMPLAINED = 'email.complained'
30
+
31
+ ALL = [
32
+ INVITATION_CREATED, INVITATION_ACCEPTED, INVITATION_DEACTIVATED,
33
+ INVITATION_EMAIL_DELIVERED, INVITATION_EMAIL_BOUNCED, INVITATION_EMAIL_OPENED,
34
+ INVITATION_LINK_CLICKED, INVITATION_REMINDER_SENT,
35
+ DEPLOYMENT_CREATED, DEPLOYMENT_DEACTIVATED,
36
+ ABTEST_STARTED, ABTEST_WINNER_DECLARED,
37
+ MEMBER_CREATED, GROUP_MEMBER_ADDED,
38
+ EMAIL_COMPLAINED
39
+ ].freeze
40
+ end
41
+
42
+ # Analytics event type constants
43
+ module AnalyticsEventTypes
44
+ WIDGET_LOADED = 'widget_loaded'
45
+ INVITATION_SENT = 'invitation_sent'
46
+ INVITATION_CLICKED = 'invitation_clicked'
47
+ INVITATION_ACCEPTED = 'invitation_accepted'
48
+ SHARE_TRIGGERED = 'share_triggered'
49
+ end
50
+
51
+ # A Vortex webhook event representing a server-side state change
52
+ #
53
+ # @attr_reader id [String] Unique event ID
54
+ # @attr_reader type [String] The semantic event type
55
+ # @attr_reader timestamp [String] ISO-8601 timestamp
56
+ # @attr_reader account_id [String] The account ID
57
+ # @attr_reader environment_id [String, nil] The environment ID
58
+ # @attr_reader source_table [String] The source table
59
+ # @attr_reader operation [String] The database operation
60
+ # @attr_reader data [Hash] Event-specific payload data
61
+ class WebhookEvent
62
+ attr_reader :id, :type, :timestamp, :account_id, :environment_id,
63
+ :source_table, :operation, :data
64
+
65
+ def initialize(attrs)
66
+ @id = attrs['id']
67
+ @type = attrs['type']
68
+ @timestamp = attrs['timestamp']
69
+ @account_id = attrs['accountId']
70
+ @environment_id = attrs['environmentId']
71
+ @source_table = attrs['sourceTable']
72
+ @operation = attrs['operation']
73
+ @data = attrs['data'] || {}
74
+ end
75
+ end
76
+
77
+ # An analytics event representing client-side behavioral telemetry
78
+ class AnalyticsEvent
79
+ attr_reader :id, :name, :account_id, :organization_id, :project_id,
80
+ :environment_id, :deployment_id, :widget_configuration_id,
81
+ :foreign_user_id, :session_id, :payload, :platform,
82
+ :segmentation, :timestamp
83
+
84
+ def initialize(attrs)
85
+ @id = attrs['id']
86
+ @name = attrs['name']
87
+ @account_id = attrs['accountId']
88
+ @organization_id = attrs['organizationId']
89
+ @project_id = attrs['projectId']
90
+ @environment_id = attrs['environmentId']
91
+ @deployment_id = attrs['deploymentId']
92
+ @widget_configuration_id = attrs['widgetConfigurationId']
93
+ @foreign_user_id = attrs['foreignUserId']
94
+ @session_id = attrs['sessionId']
95
+ @payload = attrs['payload']
96
+ @platform = attrs['platform']
97
+ @segmentation = attrs['segmentation']
98
+ @timestamp = attrs['timestamp']
99
+ end
100
+ end
101
+
102
+ # Returns true if the parsed hash is a webhook event
103
+ def self.webhook_event?(parsed)
104
+ parsed.key?('type') && !parsed.key?('name')
105
+ end
106
+
107
+ # Returns true if the parsed hash is an analytics event
108
+ def self.analytics_event?(parsed)
109
+ parsed.key?('name')
110
+ end
111
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'json'
5
+
6
+ module Vortex
7
+ # Error raised when webhook signature verification fails
8
+ class WebhookSignatureError < VortexError
9
+ def initialize(message = nil)
10
+ super(message || 'Webhook signature verification failed. Ensure you are using the raw request body and the correct signing secret.')
11
+ end
12
+ end
13
+
14
+ # Core webhook verification and parsing.
15
+ #
16
+ # This class is framework-agnostic — use it directly or with
17
+ # the Rails/Sinatra framework integrations.
18
+ #
19
+ # @example
20
+ # webhooks = Vortex::Webhooks.new(secret: ENV['VORTEX_WEBHOOK_SECRET'])
21
+ # event = webhooks.construct_event(request.body.read, request.env['HTTP_X_VORTEX_SIGNATURE'])
22
+ class Webhooks
23
+ # @param secret [String] The webhook signing secret from your Vortex dashboard
24
+ def initialize(secret:)
25
+ raise ArgumentError, 'Vortex::Webhooks requires a secret' if secret.nil? || secret.empty?
26
+
27
+ @secret = secret
28
+ end
29
+
30
+ # Verify the HMAC-SHA256 signature of an incoming webhook payload.
31
+ #
32
+ # @param payload [String] The raw request body
33
+ # @param signature [String] The value of the X-Vortex-Signature header
34
+ # @return [Boolean] true if the signature is valid
35
+ def verify_signature(payload, signature)
36
+ return false if signature.nil? || signature.empty?
37
+
38
+ expected = OpenSSL::HMAC.hexdigest('SHA256', @secret, payload)
39
+
40
+ # Timing-safe comparison to prevent timing attacks
41
+ secure_compare(signature, expected)
42
+ end
43
+
44
+ # Verify and parse an incoming webhook payload.
45
+ #
46
+ # @param payload [String] The raw request body
47
+ # @param signature [String] The value of the X-Vortex-Signature header
48
+ # @return [WebhookEvent, AnalyticsEvent] A typed event object
49
+ # @raise [WebhookSignatureError] If the signature is invalid
50
+ def construct_event(payload, signature)
51
+ raise WebhookSignatureError unless verify_signature(payload, signature)
52
+
53
+ parsed = JSON.parse(payload)
54
+
55
+ if Vortex.webhook_event?(parsed)
56
+ WebhookEvent.new(parsed)
57
+ elsif Vortex.analytics_event?(parsed)
58
+ AnalyticsEvent.new(parsed)
59
+ else
60
+ WebhookEvent.new(parsed)
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ # Timing-safe string comparison
67
+ def secure_compare(a, b)
68
+ return false unless a.bytesize == b.bytesize
69
+
70
+ OpenSSL.fixed_length_secure_compare(a, b)
71
+ rescue StandardError
72
+ false
73
+ end
74
+ end
75
+ end
data/lib/vortex.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'vortex/version'
4
4
  require 'vortex/error'
5
5
  require 'vortex/types'
6
+ require 'vortex/webhook_types'
7
+ require 'vortex/webhooks'
6
8
  require 'vortex/client'
7
9
 
8
10
  # Vortex Ruby SDK
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vortex-ruby-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.4
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vortex Software
@@ -160,6 +160,8 @@ files:
160
160
  - lib/vortex/sinatra.rb
161
161
  - lib/vortex/types.rb
162
162
  - lib/vortex/version.rb
163
+ - lib/vortex/webhook_types.rb
164
+ - lib/vortex/webhooks.rb
163
165
  - vortex-ruby-sdk.gemspec
164
166
  homepage: https://github.com/vortexsoftware/vortex-ruby-sdk
165
167
  licenses: