vortex-ruby-sdk 1.9.0 → 1.18.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.
data/README.md CHANGED
@@ -1,313 +1,782 @@
1
- # Vortex Ruby SDK
1
+ # vortex-ruby-sdk
2
2
 
3
- A Ruby SDK for the Vortex invitation system, providing seamless integration with the same functionality and API compatibility as other Vortex SDKs (Node.js, Python, Java, Go).
3
+ <!-- AUTO-GENERATED FROM SDK MANIFEST DO NOT EDIT DIRECTLY -->
4
4
 
5
- ## Features
5
+ ![Version](https://img.shields.io/badge/version-1.18.0-blue)
6
+ ![Language](https://img.shields.io/badge/language-ruby-green)
6
7
 
7
- - **JWT Generation**: Identical algorithm to other SDKs for complete compatibility
8
- - **Simplified JWT Format**: New streamlined payload with `userEmail` and `adminScopes`
9
- - **Backward Compatible**: Legacy JWT format still supported
10
- - **Complete API Coverage**: All invitation management operations
11
- - **Framework Integration**: Built-in Rails and Sinatra helpers
12
- - **Same Route Structure**: Ensures React provider compatibility
13
- - **Comprehensive Testing**: Full test coverage with RSpec
14
- - **Type Safety**: Clear method signatures and documentation
15
- - **Multiple Delivery Types**: Support for `email`, `phone`, `share`, and `internal` invitation delivery
16
- - `internal` invitations allow for customer-managed, in-app invitation flows with no external communication
8
+ **Invitation infrastructure for modern apps**
17
9
 
18
- ## Installation
10
+ Vortex handles the complete invitation lifecycle — sending invites via email/SMS/share links, tracking clicks and conversions, managing referral programs, and optimizing your invitation flows with A/B testing.
11
+ [Learn more about Vortex →](https://tryvortex.com)
12
+
13
+ ## Why This SDK?
14
+
15
+ This backend SDK securely signs user data for Vortex components. Your API key stays on your server, while the signed token is passed to the frontend where Vortex components render the invitation UI.
16
+
17
+ - Keep your API key secure — it never touches the browser
18
+ - Sign user identity for attribution — know who sent each invitation
19
+ - Control what data components can access via scoped tokens
20
+ - Verify webhook signatures for secure event handling
21
+
22
+ ## How It Works
23
+
24
+ Vortex uses a split architecture: your backend signs tokens with the SDK, and your frontend renders components that use those tokens to securely interact with Vortex.
25
+
26
+ ```
27
+ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
28
+ │ Your Server │ │ User Browser │ │ Vortex Cloud │
29
+ │ (this SDK) │ │ (component) │ │ │
30
+ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘
31
+ │ │ │
32
+ │ 1. generate_token │ │
33
+ │◄──────────────────────│ │
34
+ │ │ │
35
+ │ 2. Return token │ │
36
+ │──────────────────────►│ │
37
+ │ │ │
38
+ │ │ 3. Component calls │
39
+ │ │ API with token │
40
+ │ │──────────────────────►│
41
+ │ │ │
42
+ │ │ 4. Render UI, │
43
+ │ │ send invitations │
44
+ │ │◄──────────────────────│
45
+ │ │ │
46
+ ```
47
+
48
+ ### Integration Flow
19
49
 
20
- Add this line to your application's Gemfile:
50
+ **1. Install the backend SDK** `[backend]`
51
+
52
+ Add this SDK to your Ruby project
21
53
 
22
54
  ```ruby
23
55
  gem 'vortex-ruby-sdk'
24
56
  ```
25
57
 
26
- And then execute:
58
+ **2. Initialize the client** `[backend]`
27
59
 
28
- ```bash
29
- bundle install
60
+ Create a Vortex client with your API key (keep this on the server!)
61
+
62
+ ```ruby
63
+ require 'vortex'
64
+
65
+ client = Vortex::Client.new(ENV['VORTEX_API_KEY'])
30
66
  ```
31
67
 
32
- Or install it yourself as:
68
+ **3. Generate a token for the current user** `[backend]`
33
69
 
34
- ```bash
35
- gem install vortex-ruby-sdk
70
+ When a user loads a page with a Vortex component, generate a signed token on your server
71
+
72
+ ```ruby
73
+ token = client.generate_token(user: { id: current_user.id })
74
+ ```
75
+
76
+ **4. Pass the token to your frontend** `[backend]`
77
+
78
+ Include the token in your page response or API response
79
+
80
+ ```ruby
81
+ render json: { vortex_token: token }
82
+ ```
83
+
84
+ **5. Render a Vortex component with the token** `[frontend]`
85
+
86
+ Use the React/Angular/Web Component with the token
87
+
88
+ ```ruby
89
+ import { VortexInvite } from "@teamvortexsoftware/vortex-react";
90
+
91
+ <VortexInvite token={vortexToken} />
36
92
  ```
37
93
 
38
- ## Basic Usage
94
+ **6. Vortex handles the rest** `[vortex]`
95
+
96
+ The component securely communicates with Vortex servers, displays the invitation UI, sends emails/SMS, tracks conversions, and reports analytics
97
+
98
+ ### Security Model
99
+
100
+ > ⚠️ **Important:** Your Vortex API key is a secret that grants full access to your account. It must never be exposed to browsers or client-side code.
101
+
102
+ By signing tokens on your server, you:
103
+
104
+ - Keep your API key secret (it never leaves your server)
105
+ - Control exactly what user data is shared with components
106
+ - Ensure invitations are attributed to real, authenticated users
107
+ - Prevent abuse — users can only send invitations as themselves
108
+
109
+ #### When Signing is Optional
110
+
111
+ Token signing is controlled by your component configuration in the Vortex dashboard. If "Require Secure Token" is enabled, requests without a valid token will be rejected. If disabled (e.g., for public referral programs), components work without backend signing.
112
+
113
+ ---
114
+
115
+ ## Quick Start
116
+
117
+ Generate a secure token for Vortex components
39
118
 
40
119
  ```ruby
41
120
  require 'vortex'
42
121
 
43
- # Initialize the client
44
122
  client = Vortex::Client.new(ENV['VORTEX_API_KEY'])
45
123
 
46
- # Create a user object
47
- user = {
48
- id: 'user-123',
49
- email: 'user@example.com',
50
- user_name: 'Jane Doe', # Optional: user's display name
51
- user_avatar_url: 'https://example.com/avatars/jane.jpg', # Optional: user's avatar URL
52
- admin_scopes: ['autojoin'] # Optional: grants autojoin admin privileges
53
- }
124
+ # Generate a token for the current user
125
+ token = client.generate_token(user: { id: 'user-123', email: 'user@example.com' })
54
126
 
55
- # Generate JWT
56
- jwt = client.generate_jwt(user: user)
127
+ # Pass the token to your frontend component
57
128
 
58
- # Get invitations by target
59
- invitations = client.get_invitations_by_target('email', 'user@example.com')
129
+ ```
60
130
 
61
- # Accept an invitation
62
- client.accept_invitation('inv-123', { email: 'user@example.com' })
131
+ ## Installation
63
132
 
64
- # Get invitations by group
65
- group_invitations = client.get_invitations_by_group('team', 'team1')
133
+ ```bash
134
+ gem install vortex-ruby-sdk
66
135
  ```
67
136
 
68
- ## Rails Integration
137
+ ## Initialization
69
138
 
70
- Create a controller with Vortex routes:
139
+ ```ruby
140
+ client = Vortex::Client.new(ENV['VORTEX_API_KEY'])
141
+ ```
142
+
143
+ ### Environment Variables
144
+
145
+ | Variable | Required | Description |
146
+ | ---------------- | -------- | ------------------- |
147
+ | `VORTEX_API_KEY` | ✓ | Your Vortex API key |
148
+
149
+ ## Core Methods
150
+
151
+ These are the methods you'll use most often.
152
+
153
+ ### `generate_token()`
154
+
155
+ Generate a signed token for use with Vortex widgets. This method generates a signed JWT token containing your payload data. The token can be passed to widgets via the `token` prop to authenticate and authorize the request.
156
+
157
+ **Signature:**
71
158
 
72
159
  ```ruby
73
- # app/controllers/vortex_controller.rb
74
- class VortexController < ApplicationController
75
- include Vortex::Rails::Controller
76
-
77
- private
78
-
79
- def authenticate_vortex_user
80
- # Return user data hash or nil
81
- admin_scopes = []
82
- admin_scopes << 'autojoin' if current_user.admin?
83
-
84
- {
85
- id: current_user.id,
86
- email: current_user.email,
87
- admin_scopes: admin_scopes
88
- }
89
- end
160
+ generate_token(payload, options = nil)
161
+ ```
90
162
 
91
- def authorize_vortex_operation(operation, user)
92
- # Implement your authorization logic
93
- case operation
94
- when 'JWT', 'GET_INVITATIONS'
95
- true
96
- when 'REVOKE_INVITATION'
97
- user[:admin_scopes]&.include?('autojoin')
98
- else
99
- false
100
- end
101
- end
163
+ **Parameters:**
102
164
 
103
- def vortex_client
104
- @vortex_client ||= Vortex::Client.new(Rails.application.credentials.vortex_api_key)
105
- end
106
- end
165
+ | Name | Type | Required | Description |
166
+ | --------- | ------ | -------- | ------------------------------------------------- |
167
+ | `payload` | `Hash` | ✓ | Data to sign (user, component, scope, vars, etc.) |
168
+ | `options` | `Hash` | ✓ | Optional configuration |
169
+
170
+ **Returns:** `String`
171
+ — Signed JWT token
172
+
173
+ **Example:**
174
+
175
+ ```ruby
176
+ # token = client.generate_token({ user: { id: 'user-123' } })
177
+ #
178
+ # token = client.generate_token({
179
+ # component: 'widget-abc',
180
+ # user: { id: 'user-123', name: 'Peter', email: 'peter@example.com' },
181
+ # scope: 'workspace_456',
182
+ # vars: { company_name: 'Acme' }
183
+ # })
184
+ #
185
+ # token = client.generate_token(payload, { expires_in: '1h' })
186
+ # token = client.generate_token(payload, { expires_in: 3600 }) # seconds
107
187
  ```
108
188
 
109
- Add routes to `config/routes.rb`:
189
+ _Added in v0.8.0_
190
+
191
+ ---
192
+
193
+ ### `get_invitation()`
194
+
195
+ Get a specific invitation by ID
196
+
197
+ **Signature:**
110
198
 
111
199
  ```ruby
112
- Rails.application.routes.draw do
113
- scope '/api/vortex', controller: 'vortex' do
114
- post 'jwt', action: 'generate_jwt'
115
- get 'invitations', action: 'get_invitations_by_target'
116
- get 'invitations/:invitation_id', action: 'get_invitation'
117
- delete 'invitations/:invitation_id', action: 'revoke_invitation'
118
- post 'invitations/accept', action: 'accept_invitations'
119
- get 'invitations/by-group/:group_type/:group_id', action: 'get_invitations_by_group'
120
- delete 'invitations/by-group/:group_type/:group_id', action: 'delete_invitations_by_group'
121
- post 'invitations/:invitation_id/reinvite', action: 'reinvite'
122
- end
123
- end
200
+ get_invitation(invitation_id)
124
201
  ```
125
202
 
126
- ## Sinatra Integration
203
+ **Parameters:**
204
+
205
+ | Name | Type | Required | Description |
206
+ | --------------- | -------- | -------- | ----------------- |
207
+ | `invitation_id` | `String` | ✓ | The invitation ID |
208
+
209
+ **Returns:** `String`
210
+ — The invitation data
211
+
212
+ _Added in v0.1.0_
213
+
214
+ ---
215
+
216
+ ### `accept_invitation()`
217
+
218
+ Accept a single invitation (recommended method) This is the recommended method for accepting invitations.
219
+
220
+ **Signature:**
127
221
 
128
222
  ```ruby
129
- require 'sinatra/base'
130
- require 'vortex/sinatra'
223
+ accept_invitation(invitation_id, user)
224
+ ```
131
225
 
132
- class MyApp < Sinatra::Base
133
- register Vortex::Sinatra
226
+ **Parameters:**
134
227
 
135
- configure do
136
- set :vortex_api_key, ENV['VORTEX_API_KEY']
137
- end
228
+ | Name | Type | Required | Description |
229
+ | --------------- | -------- | -------- | ----------------------------------- |
230
+ | `invitation_id` | `String` | ✓ | Single invitation ID to accept |
231
+ | `user` | `Hash` | ✓ | User hash with :email and/or :phone |
138
232
 
139
- def authenticate_vortex_user
140
- # Implement authentication logic
141
- user_id = request.env['HTTP_X_USER_ID']
142
- return nil unless user_id
233
+ **Returns:** `String`
234
+ The accepted invitation result
143
235
 
144
- {
145
- id: user_id,
146
- email: 'user@example.com',
147
- admin_scopes: [] # Optional
148
- }
149
- end
236
+ **Example:**
150
237
 
151
- def authorize_vortex_operation(operation, user)
152
- # Implement authorization logic
153
- user != nil
154
- end
155
- end
238
+ ```ruby
239
+ # user = { email: 'user@example.com', name: 'John Doe' }
240
+ # result = client.accept_invitation('inv-123', user)
156
241
  ```
157
242
 
158
- ## API Methods
243
+ _Added in v0.6.0_
159
244
 
160
- All methods match the functionality of other Vortex SDKs:
245
+ ---
161
246
 
162
- ### JWT Generation
247
+ ## All Methods
163
248
 
164
- - `generate_jwt(user:, extra: nil)` - Generate JWT token
165
- - `user`: Hash with `:id`, `:email`, and optional `:admin_scopes` array
166
- - `extra`: Optional hash with additional properties to include in JWT payload
249
+ <details>
250
+ <summary>Click to expand full method reference</summary>
167
251
 
168
- ### Invitation Management
252
+ ### `get_invitations_by_target()`
169
253
 
170
- - `get_invitations_by_target(target_type, target_value)` - Get invitations by target
171
- - `get_invitation(invitation_id)` - Get specific invitation
172
- - `revoke_invitation(invitation_id)` - Revoke invitation
173
- - `accept_invitation(invitation_id, user)` - Accept an invitation
174
- - `get_invitations_by_group(group_type, group_id)` - Get group invitations
175
- - `delete_invitations_by_group(group_type, group_id)` - Delete group invitations
176
- - `reinvite(invitation_id)` - Reinvite user
177
- - `sync_internal_invitation(creator_id, target_value, action, component_id)` - Sync internal invitation action
254
+ Get invitations by target
178
255
 
179
- ## Route Structure
256
+ **Signature:**
257
+
258
+ ```ruby
259
+ get_invitations_by_target(target_type, target_value)
260
+ ```
261
+
262
+ **Parameters:**
180
263
 
181
- The SDK provides these routes (same as other SDKs for React provider compatibility):
264
+ | Name | Type | Required | Description |
265
+ | -------------- | -------- | -------- | --------------------------------------------- |
266
+ | `target_type` | `String` | ✓ | Type of target (email, sms) |
267
+ | `target_value` | `String` | ✓ | Value of target (email address, phone number) |
182
268
 
183
- - `POST /api/vortex/jwt`
184
- - `GET /api/vortex/invitations?targetType=email&targetValue=user@example.com`
185
- - `GET /api/vortex/invitations/:id`
186
- - `DELETE /api/vortex/invitations/:id`
187
- - `POST /api/vortex/invitations/accept`
188
- - `GET /api/vortex/invitations/by-group/:type/:id`
189
- - `DELETE /api/vortex/invitations/by-group/:type/:id`
190
- - `POST /api/vortex/invitations/:id/reinvite`
191
- - `POST /api/vortex/invitations/sync-internal-invitation`
269
+ **Returns:** `String`
270
+ List of invitations
192
271
 
193
- ## Sync Internal Invitation
272
+ _Added in v0.1.0_
194
273
 
195
- If you're using `internal` delivery type invitations and managing the invitation flow within your own application, you can sync invitation decisions back to Vortex when users accept or decline invitations in your system.
274
+ ---
275
+
276
+ ### `revoke_invitation()`
277
+
278
+ Revoke (delete) an invitation
279
+
280
+ **Signature:**
196
281
 
197
282
  ```ruby
198
- # Sync an internal invitation action
199
- result = client.sync_internal_invitation(
200
- 'user-123', # creator_id - The inviter's user ID in your system
201
- 'user-456', # target_value - The invitee's user ID in your system
202
- 'accepted', # action - "accepted" or "declined"
203
- 'component-uuid' # component_id - The widget component UUID
204
- )
205
-
206
- puts "Processed: #{result['processed']}"
207
- puts "Invitation IDs: #{result['invitationIds']}"
283
+ revoke_invitation(invitation_id)
208
284
  ```
209
285
 
210
286
  **Parameters:**
211
- - `creator_id` (String) — The inviter's user ID in your system
212
- - `target_value` (String) — The invitee's user ID in your system
213
- - `action` ("accepted" | "declined") — The invitation decision
214
- - `component_id` (String) — The widget component UUID
215
287
 
216
- **Response:**
217
- - `processed` (Integer) Count of invitations processed
218
- - `invitationIds` (Array<String>) IDs of processed invitations
288
+ | Name | Type | Required | Description |
289
+ | --------------- | -------- | -------- | --------------------------- |
290
+ | `invitation_id` | `String` | ✓ | The invitation ID to revoke |
291
+
292
+ **Returns:** `String`
293
+ — Success response
219
294
 
220
- **Use cases:**
221
- - You handle invitation delivery through your own in-app notifications or UI
222
- - Users accept/decline invitations within your application
223
- - You need to keep Vortex updated with the invitation status
295
+ _Added in v0.1.0_
224
296
 
225
- ## JWT Payload Structure
297
+ ---
226
298
 
227
- The SDK generates JWTs with the following payload structure:
299
+ ### `accept_invitations()`
300
+
301
+ Accept invitations using the new User format (preferred) Supports three formats: 1. User hash (preferred): { email: '...', phone: '...', name: '...' } 2. Target hash (deprecated): { type: 'email', value: '...' } 3. Array of targets (deprecated): [{ type: 'email', value: '...' }, ...]
302
+
303
+ **Signature:**
228
304
 
229
305
  ```ruby
230
- {
231
- userId: 'user-123',
232
- userEmail: 'user@example.com',
233
- adminScopes: ['autojoin'], # Full array included if admin_scopes provided
234
- expires: 1234567890
235
- }
306
+ accept_invitations(invitation_ids, user_or_target)
236
307
  ```
237
308
 
238
- Additional properties from the `extra` parameter are merged into the payload.
309
+ **Parameters:**
239
310
 
240
- ## Error Handling
311
+ | Name | Type | Required | Description |
312
+ | ---------------- | --------------- | -------- | ------------------------------------------------------------ |
313
+ | `invitation_ids` | `Array<String>` | ✓ | List of invitation IDs to accept |
314
+ | `user_or_target` | `Hash, Array` | ✓ | User hash with :email/:phone/:name keys, OR legacy target(s) |
241
315
 
242
- All methods raise `Vortex::VortexError` on failures:
316
+ **Returns:** `String`
317
+ — The accepted invitation result
318
+
319
+ **Example:**
243
320
 
244
321
  ```ruby
245
- begin
246
- jwt = client.generate_jwt(
247
- user: {
248
- id: 'user-123',
249
- email: 'user@example.com'
250
- }
251
- )
252
- rescue Vortex::VortexError => e
253
- logger.error "Vortex error: #{e.message}"
254
- end
322
+ # user = { email: 'user@example.com', name: 'John Doe' }
323
+ # result = client.accept_invitations(['inv-123'], user)
324
+ #
325
+ # target = { type: 'email', value: 'user@example.com' }
326
+ # result = client.accept_invitations(['inv-123'], target)
255
327
  ```
256
328
 
257
- ## Development
329
+ _Added in v0.1.0_
258
330
 
259
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt.
331
+ ---
260
332
 
261
- To install this gem onto your local machine, run `bundle exec rake install`.
333
+ ### `get_invitations_by_scope()`
262
334
 
263
- ## Contributing
335
+ Get invitations by group
264
336
 
265
- ## Webhooks
337
+ **Signature:**
338
+
339
+ ```ruby
340
+ get_invitations_by_scope(scope_type, scope)
341
+ ```
342
+
343
+ **Parameters:**
344
+
345
+ | Name | Type | Required | Description |
346
+ | ------------ | -------- | -------- | -------------- |
347
+ | `scope_type` | `String` | ✓ | The group type |
348
+ | `scope` | `String` | ✓ | The group ID |
349
+
350
+ **Returns:** `String`
351
+ — List of invitations for the group
352
+
353
+ _Added in v0.4.0_
266
354
 
267
- The SDK provides built-in support for verifying and parsing incoming webhook events from Vortex.
355
+ ---
356
+
357
+ ### `delete_invitations_by_scope()`
358
+
359
+ Delete invitations by group
360
+
361
+ **Signature:**
268
362
 
269
363
  ```ruby
270
- require 'vortex'
364
+ delete_invitations_by_scope(scope_type, scope)
365
+ ```
366
+
367
+ **Parameters:**
368
+
369
+ | Name | Type | Required | Description |
370
+ | ------------ | -------- | -------- | -------------- |
371
+ | `scope_type` | `String` | ✓ | The group type |
372
+ | `scope` | `String` | ✓ | The group ID |
373
+
374
+ **Returns:** `String`
375
+ — Success response
376
+
377
+ _Added in v0.4.0_
378
+
379
+ ---
271
380
 
272
- webhooks = Vortex::Webhooks.new(secret: ENV['VORTEX_WEBHOOK_SECRET'])
381
+ ### `reinvite()`
273
382
 
274
- # In your HTTP handler (Rails example):
383
+ Reinvite a user
384
+
385
+ **Signature:**
386
+
387
+ ```ruby
388
+ reinvite(invitation_id)
389
+ ```
390
+
391
+ **Parameters:**
392
+
393
+ | Name | Type | Required | Description |
394
+ | --------------- | -------- | -------- | ----------------------------- |
395
+ | `invitation_id` | `String` | ✓ | The invitation ID to reinvite |
396
+
397
+ **Returns:** `String`
398
+ — The reinvited invitation result
399
+
400
+ _Added in v0.2.0_
401
+
402
+ ---
403
+
404
+ ### `get_autojoin_domains()`
405
+
406
+ Get autojoin domains configured for a specific scope
407
+
408
+ **Signature:**
409
+
410
+ ```ruby
411
+ get_autojoin_domains(scope_type, scope)
412
+ ```
413
+
414
+ **Parameters:**
415
+
416
+ | Name | Type | Required | Description |
417
+ | ------------ | -------- | -------- | ----------------------------------------------------------- |
418
+ | `scope_type` | `String` | ✓ | The type of scope (e.g., "organization", "team", "project") |
419
+ | `scope` | `String` | ✓ | The scope identifier (customer's group ID) |
420
+
421
+ **Returns:** `String`
422
+ — Response with :autojoin_domains array and :invitation
423
+
424
+ **Example:**
425
+
426
+ ```ruby
427
+ # result = client.get_autojoin_domains('organization', 'acme-org')
428
+ # result['autojoinDomains'].each do |domain|
429
+ # puts "Domain: #{domain['domain']}"
430
+ # end
431
+ ```
432
+
433
+ _Added in v0.6.0_
434
+
435
+ ---
436
+
437
+ ### `configure_autojoin()`
438
+
439
+ Configure autojoin domains for a specific scope This endpoint syncs autojoin domains - it will add new domains, remove domains not in the provided list, and deactivate the autojoin invitation if all domains are removed (empty array).
440
+
441
+ **Signature:**
442
+
443
+ ```ruby
444
+ configure_autojoin(scope, scope_type, domains, component_id, scope_name = nil, metadata = nil)
445
+ ```
446
+
447
+ **Parameters:**
448
+
449
+ | Name | Type | Required | Description |
450
+ | -------------- | --------------- | -------- | ------------------------------------------------ |
451
+ | `scope` | `String` | ✓ | The scope identifier (customer's group ID) |
452
+ | `scope_type` | `String` | ✓ | The type of scope (e.g., "organization", "team") |
453
+ | `domains` | `Array<String>` | ✓ | Array of domains to configure for autojoin |
454
+ | `component_id` | `String` | ✓ | The component ID |
455
+ | `scope_name` | `String, nil` | ✓ | Optional display name for the scope |
456
+ | `metadata` | `Hash, nil` | ✓ | Optional metadata to attach to the invitation |
457
+
458
+ **Returns:** `String`
459
+ — Response with :autojoin_domains array and :invitation
460
+
461
+ **Example:**
462
+
463
+ ```ruby
464
+ # result = client.configure_autojoin(
465
+ # 'acme-org',
466
+ # 'organization',
467
+ # ['acme.com', 'acme.org'],
468
+ # 'component-123',
469
+ # 'Acme Corporation'
470
+ # )
471
+ ```
472
+
473
+ _Added in v0.6.0_
474
+
475
+ ---
476
+
477
+ </details>
478
+
479
+ ## Types
480
+
481
+ <details>
482
+ <summary>Click to expand type definitions</summary>
483
+
484
+ ### `GenerateTokenPayload`
485
+
486
+ Payload for generate_token() - used to generate secure tokens for Vortex components
487
+
488
+ | Field | Type | Required | Description |
489
+ | ----------- | ------------------ | -------- | ---------------------------------------------------------------------- |
490
+ | `user` | `Hash (TokenUser)` | | The authenticated user who will be using the Vortex component |
491
+ | `component` | `String` | | Component ID to generate token for (from your Vortex dashboard) |
492
+ | `scope` | `String` | | Scope identifier to restrict invitations (format: "scopeType:scopeId") |
493
+ | `vars` | `Hash` | | Custom variables to pass to the component for template rendering |
494
+
495
+ ### `TokenUser`
496
+
497
+ User data for token generation - represents the authenticated user sending invitations
498
+
499
+ | Field | Type | Required | Description |
500
+ | ----------------------- | --------------- | -------- | ----------------------------------------------------------------------------- |
501
+ | `id` | `String` | ✓ | Unique identifier for the user in your system. Used to attribute invitations. |
502
+ | `email` | `String` | | User's email address. Used for reply-to in invitation emails. |
503
+ | `name` | `String` | | Display name shown to invitation recipients (e.g., "John invited you") |
504
+ | `avatar_url` | `String` | | URL to user's avatar image. Displayed in invitation emails and widgets. |
505
+ | `admin_scopes` | `Array<String>` | | List of scope IDs where this user has admin privileges |
506
+ | `allowed_email_domains` | `Array<String>` | | Restrict invitations to specific email domains (e.g., ["acme.com"]) |
507
+
508
+ ### `AcceptUser`
509
+
510
+ User data for accepting invitations - identifies who accepted the invitation
511
+
512
+ | Field | Type | Required | Description |
513
+ | ------------- | --------- | -------- | ---------------------------------------------------------------------------------- |
514
+ | `email` | `String` | | Email address of the accepting user. At least one of email or phone is required. |
515
+ | `phone` | `String` | | Phone number with country code. At least one of email or phone is required. |
516
+ | `name` | `String` | | Display name of the accepting user (shown in notifications to inviter) |
517
+ | `is_existing` | `Boolean` | | Whether user was already registered. true=existing, false=new signup, nil=unknown. |
518
+
519
+ ### `CreateInvitationTarget`
520
+
521
+ Target specification when creating an invitation - where to send the invite
522
+
523
+ | Field | Type | Required | Description |
524
+ | ------- | -------- | -------- | ---------------------------------------------------------------------------------- |
525
+ | `type` | `String` | ✓ | Delivery channel: "email", "phone", "share", or "internal" |
526
+ | `value` | `String` | ✓ | Target address: email address, phone number with country code, or internal user ID |
527
+ | `name` | `String` | | Display name of the recipient (used in email greetings) |
528
+
529
+ ### `CreateInvitationScope`
530
+
531
+ Scope specification when creating an invitation - what group/team to invite into
532
+
533
+ | Field | Type | Required | Description |
534
+ | ---------- | -------- | -------- | ------------------------------------------------------- |
535
+ | `type` | `String` | ✓ | Scope type (e.g., "team", "organization", "workspace") |
536
+ | `group_id` | `String` | ✓ | Your internal identifier for this scope/group |
537
+ | `name` | `String` | ✓ | Display name for the scope (shown in invitation emails) |
538
+
539
+ ### `Identifier`
540
+
541
+ Email or phone identifier for looking up users
542
+
543
+ | Field | Type | Required | Description |
544
+ | ------- | -------- | -------- | --------------------------------------------------------------- |
545
+ | `type` | `String` | ✓ | Identifier type: "email" or "phone" |
546
+ | `value` | `String` | ✓ | The email address or phone number (with country code for phone) |
547
+
548
+ ### `ConfigureAutojoinRequest`
549
+
550
+ Request to configure autojoin domains for a scope
551
+
552
+ | Field | Type | Required | Description |
553
+ | ------------ | --------------- | -------- | ----------------------------------------------------------------- |
554
+ | `scope_type` | `String` | ✓ | Type of scope (e.g., "team", "workspace") |
555
+ | `scope_id` | `String` | ✓ | Your internal identifier for the scope |
556
+ | `domains` | `Array<String>` | ✓ | List of email domains to enable autojoin for (e.g., ["acme.com"]) |
557
+
558
+ ### `SyncInternalInvitationRequest`
559
+
560
+ Request to sync an internal invitation (for tracking invitations made outside Vortex)
561
+
562
+ | Field | Type | Required | Description |
563
+ | ------------ | ------------------------------- | -------- | ------------------------------------------------------------ |
564
+ | `inviter_id` | `String` | ✓ | Your internal user ID for the person who sent the invitation |
565
+ | `target` | `Hash (CreateInvitationTarget)` | ✓ | The invitation recipient |
566
+ | `scopes` | `Array<Hash>` | | Scopes/groups the invitation grants access to |
567
+
568
+ ### `InvitationResult`
569
+
570
+ Complete invitation details as returned by the Vortex API
571
+
572
+ | Field | Type | Required | Description |
573
+ | -------------------------- | ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
574
+ | `id` | `String` | ✓ | Unique identifier for this invitation |
575
+ | `account_id` | `String` | ✓ | Your Vortex account ID |
576
+ | `click_throughs` | `Integer` | ✓ | Number of times the invitation link was clicked |
577
+ | `form_submission_data` | `Hash \| nil` | | Invitation form data submitted by the user, including invitee identifiers (such as email addresses, phone numbers, or internal IDs) and the values of any custom fields. |
578
+ | `configuration_attributes` | `Hash \| nil` | | Deprecated: Use form_submission_data instead. Contains the same data. |
579
+ | `created_at` | `String` | ✓ | ISO 8601 timestamp when the invitation was created |
580
+ | `deactivated` | `Boolean` | ✓ | Whether this invitation has been revoked or expired |
581
+ | `delivery_count` | `Integer` | ✓ | Number of times the invitation was sent (including reminders) |
582
+ | `delivery_types` | `Array<String>` | ✓ | Channels used to deliver: "email", "phone", "share", "internal" |
583
+ | `foreign_creator_id` | `String` | ✓ | Your internal user ID for the person who created this invitation |
584
+ | `invitation_type` | `String` | ✓ | Type: "single_use", "multi_use", or "autojoin" |
585
+ | `status` | `String` | ✓ | Current status: queued, sending, sent, delivered, accepted, shared |
586
+ | `target` | `Array<Hash>` | ✓ | List of invitation recipients with their contact info and status |
587
+ | `views` | `Integer` | ✓ | Number of times the invitation page was viewed |
588
+ | `groups` | `Array<Hash>` | ✓ | Scopes (teams/orgs) this invitation grants access to |
589
+ | `expired` | `Boolean` | ✓ | Whether this invitation has passed its expiration date |
590
+ | `expires` | `String` | | ISO 8601 timestamp when this invitation expires |
591
+ | `inviter` | `Hash (Inviter)` | | Information about who sent the invitation |
592
+
593
+ ### `InvitationTarget`
594
+
595
+ Target recipient of an invitation (from API response)
596
+
597
+ | Field | Type | Required | Description |
598
+ | ------------ | -------- | -------- | ----------------------------------------------------------------------- |
599
+ | `type` | `String` | ✓ | Delivery channel: "email", "phone", "share", or "internal" |
600
+ | `value` | `String` | ✓ | Target address: email, phone number with country code, or share link ID |
601
+ | `name` | `String` | | Display name of the recipient |
602
+ | `avatar_url` | `String` | | Avatar URL for the recipient |
603
+ | `status` | `String` | | Delivery status for this specific target |
604
+
605
+ ### `InvitationScope`
606
+
607
+ Scope/group that the invitation grants access to (from API response)
608
+
609
+ | Field | Type | Required | Description |
610
+ | ------------ | -------- | -------- | ------------------------------------------------------ |
611
+ | `id` | `String` | ✓ | Vortex internal UUID for this scope record |
612
+ | `account_id` | `String` | ✓ | Your Vortex account ID |
613
+ | `group_id` | `String` | ✓ | Your internal scope/group identifier |
614
+ | `type` | `String` | ✓ | Scope type (e.g., "team", "organization", "workspace") |
615
+ | `name` | `String` | ✓ | Display name for the scope |
616
+ | `created_at` | `String` | ✓ | ISO 8601 timestamp when the scope was created |
617
+
618
+ ### `InvitationAcceptance`
619
+
620
+ Details about an invitation acceptance event
621
+
622
+ | Field | Type | Required | Description |
623
+ | --------------- | --------- | -------- | ----------------------------------------------- |
624
+ | `id` | `String` | ✓ | Unique identifier for this acceptance record |
625
+ | `invitation_id` | `String` | ✓ | ID of the invitation that was accepted |
626
+ | `email` | `String` | | Email of the user who accepted |
627
+ | `phone` | `String` | | Phone of the user who accepted |
628
+ | `name` | `String` | | Name of the user who accepted |
629
+ | `is_existing` | `Boolean` | | Whether the user already had an account |
630
+ | `created_at` | `String` | ✓ | ISO 8601 timestamp when the acceptance occurred |
631
+
632
+ ### `Inviter`
633
+
634
+ Information about the user who sent an invitation
635
+
636
+ | Field | Type | Required | Description |
637
+ | ------------ | -------- | -------- | ------------------------------------- |
638
+ | `id` | `String` | ✓ | Your internal user ID for the inviter |
639
+ | `email` | `String` | | Email address of the inviter |
640
+ | `name` | `String` | | Display name of the inviter |
641
+ | `avatar_url` | `String` | | Avatar URL of the inviter |
642
+
643
+ ### `AutojoinDomain`
644
+
645
+ Autojoin domain configuration - users with matching email domains automatically join
646
+
647
+ | Field | Type | Required | Description |
648
+ | -------- | -------- | -------- | ------------------------------------------------------ |
649
+ | `id` | `String` | ✓ | Unique identifier for this autojoin configuration |
650
+ | `domain` | `String` | ✓ | Email domain that triggers autojoin (e.g., "acme.com") |
651
+
652
+ ### `AutojoinDomainsResponse`
653
+
654
+ Response from get_autojoin_domains()
655
+
656
+ | Field | Type | Required | Description |
657
+ | --------- | ----------------------- | -------- | ----------------------------------- |
658
+ | `domains` | `Array<AutojoinDomain>` | ✓ | List of configured autojoin domains |
659
+
660
+ ### `SyncInternalInvitationResponse`
661
+
662
+ Response from sync_internal_invitation()
663
+
664
+ | Field | Type | Required | Description |
665
+ | ------------ | ------------------------- | -------- | ------------------------------------------------------------------- |
666
+ | `invitation` | `Hash (InvitationResult)` | ✓ | The created or updated invitation |
667
+ | `created` | `Boolean` | ✓ | true if a new invitation was created, false if existing was updated |
668
+
669
+ ### `VortexWebhookEvent`
670
+
671
+ Webhook event payload delivered to your endpoint
672
+
673
+ | Field | Type | Required | Description |
674
+ | ----------- | -------- | -------- | ---------------------------------------------------------- |
675
+ | `id` | `String` | ✓ | Unique identifier for this webhook delivery |
676
+ | `type` | `String` | ✓ | Event type (e.g., "invitation.accepted", "member.created") |
677
+ | `timestamp` | `String` | ✓ | ISO 8601 timestamp when the event occurred |
678
+ | `data` | `Hash` | ✓ | Event-specific payload data |
679
+
680
+ </details>
681
+
682
+ ## Webhooks
683
+
684
+ Webhooks let your server receive real-time notifications when events happen in Vortex. Use them to sync invitation state with your database, trigger onboarding flows, update your CRM, or send internal notifications.
685
+
686
+ ### Setup
687
+
688
+ 1. Go to your Vortex dashboard → Integrations → Webhooks tab
689
+ 2. Click "Add Webhook"
690
+ 3. Enter your endpoint URL (must be HTTPS in production)
691
+ 4. Copy the signing secret — you'll use this to verify webhook signatures
692
+ 5. Select which events you want to receive
693
+
694
+ ### Verifying Webhooks
695
+
696
+ Always verify webhook signatures using `Vortex::Webhooks.verify_signature()` to ensure requests are from Vortex.
697
+ The signature is sent in the `X-Vortex-Signature` header.
698
+
699
+ ### Example: Rails webhook handler
700
+
701
+ ```ruby
275
702
  class WebhooksController < ApplicationController
276
703
  skip_before_action :verify_authenticity_token
277
704
 
278
- def create
279
- payload = request.body.read
705
+ def vortex
706
+ webhooks = Vortex::Webhooks.new(ENV['VORTEX_WEBHOOK_SECRET'])
707
+
708
+ payload = request.raw_post
280
709
  signature = request.headers['X-Vortex-Signature']
281
710
 
282
- begin
283
- event = webhooks.construct_event(payload, signature)
284
- rescue Vortex::WebhookSignatureError
285
- head :bad_request
286
- return
711
+ # Verify the signature
712
+ unless webhooks.verify_signature(payload, signature)
713
+ return render json: { error: 'Invalid signature' }, status: 400
287
714
  end
288
715
 
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}"
716
+ # Parse the event
717
+ event = webhooks.parse_event(payload)
718
+
719
+ case event['type']
720
+ when 'invitation.accepted'
721
+ # User accepted an invitation — activate their account
722
+ Rails.logger.info "Accepted: #{event['data']}"
723
+ when 'member.created'
724
+ # New member joined via invitation
725
+ Rails.logger.info "New member: #{event['data']}"
294
726
  end
295
727
 
296
- head :ok
728
+ render json: { received: true }
297
729
  end
298
730
  end
731
+
299
732
  ```
300
733
 
301
- ### Event Type Constants
734
+ ### Common Use Cases
302
735
 
303
- ```ruby
304
- if event.type == Vortex::WebhookEventTypes::INVITATION_ACCEPTED
305
- # Handle invitation accepted
306
- end
307
- ```
736
+ **Activate users on acceptance**
737
+
738
+ When invitation.accepted fires, mark the user as active in your database and trigger your onboarding flow.
739
+
740
+ **Track invitation performance**
741
+
742
+ Monitor email.delivered, email.opened, and link.clicked events to measure invitation funnel metrics.
743
+
744
+ **Sync team membership**
745
+
746
+ Use member.created and group.member.added to keep your internal membership records in sync.
747
+
748
+ **Alert on delivery issues**
749
+
750
+ Watch for email.bounced events to proactively reach out via alternative channels.
751
+
752
+ ### Supported Events
753
+
754
+ | Event | Description |
755
+ | ---------------------------- | ---------------------------------------------------- |
756
+ | `invitation.created` | A new invitation was created |
757
+ | `invitation.accepted` | An invitation was accepted by the recipient |
758
+ | `invitation.deactivated` | An invitation was deactivated (revoked or expired) |
759
+ | `invitation.email.delivered` | Invitation email was successfully delivered |
760
+ | `invitation.email.bounced` | Invitation email bounced (invalid address) |
761
+ | `invitation.email.opened` | Recipient opened the invitation email |
762
+ | `invitation.link.clicked` | Recipient clicked the invitation link |
763
+ | `invitation.reminder.sent` | A reminder email was sent for a pending invitation |
764
+ | `member.created` | A new member was created from an accepted invitation |
765
+ | `group.member.added` | A member was added to a scope/group |
766
+ | `deployment.created` | A new deployment configuration was created |
767
+ | `deployment.deactivated` | A deployment was deactivated |
768
+ | `abtest.started` | An A/B test was started |
769
+ | `abtest.winner_declared` | An A/B test winner was declared |
770
+ | `email.complained` | Recipient marked the email as spam |
771
+
772
+ ## Error Handling
773
+
774
+ All SDK errors extend `VortexError`.
308
775
 
309
- Bug reports and pull requests are welcome on GitHub at https://github.com/vortexsoftware/vortex-ruby-sdk.
776
+ | Error | Description |
777
+ | ------------- | ---------------------------------------------------------------------------------------- |
778
+ | `VortexError` | Raised for validation errors (e.g., missing API key, invalid parameters) or API failures |
310
779
 
311
- ## License
780
+ ---
312
781
 
313
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
782
+ <!-- Generated from SDK v1.18.0 manifest -->