vortex-ruby-sdk 1.15.0 → 1.18.0.pre.20260427181620
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 +4 -4
- data/README.md +675 -238
- data/lib/vortex/client.rb +13 -11
- data/lib/vortex/types.rb +4 -2
- data/lib/vortex/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c1d76a55b4ddd17c08d1dcbd45a393e9bed146e1e76dcc0526bcd80fa57710e
|
|
4
|
+
data.tar.gz: 6ccf49c2301554141b1591e674147b58f9841dd5471106d057611c5731685fab
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 98cf940bc167c1c6a5d81875829a6281a15bd570ff17bff5430fce7f881d761840979507533e8926deddb4bb8bae970b5d80a2a0b9c80bc61d42efa4e51350d3
|
|
7
|
+
data.tar.gz: 53de4c4f581d6fc5a251887f4159a013f712da9e5f86a13cd442c00eef8deaaaa7af1e206374bb3163819ada40d5cb9509d99a6d4727b102a8f728c7da15b878
|
data/README.md
CHANGED
|
@@ -1,345 +1,782 @@
|
|
|
1
|
-
#
|
|
1
|
+
# vortex-ruby-sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<!-- AUTO-GENERATED FROM SDK MANIFEST — DO NOT EDIT DIRECTLY -->
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
|
+

|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
58
|
+
**2. Initialize the client** `[backend]`
|
|
27
59
|
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
68
|
+
**3. Generate a token for the current user** `[backend]`
|
|
33
69
|
|
|
34
|
-
|
|
35
|
-
|
|
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 })
|
|
36
74
|
```
|
|
37
75
|
|
|
38
|
-
|
|
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} />
|
|
92
|
+
```
|
|
93
|
+
|
|
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
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
}
|
|
54
|
-
|
|
55
|
-
# Generate JWT
|
|
56
|
-
jwt = client.generate_jwt(user: user)
|
|
57
|
-
|
|
58
|
-
# Get invitations by target
|
|
59
|
-
invitations = client.get_invitations_by_target('email', 'user@example.com')
|
|
60
|
-
|
|
61
|
-
# Accept an invitation
|
|
62
|
-
client.accept_invitation('inv-123', { email: 'user@example.com' })
|
|
63
|
-
|
|
64
|
-
# Accept with new vs existing user tracking
|
|
65
|
-
# is_existing: true if user was already registered, false if new signup
|
|
66
|
-
client.accept_invitation('inv-123', {
|
|
67
|
-
email: 'user@example.com',
|
|
68
|
-
is_existing: false # New user signup
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
# Get invitations by scope
|
|
72
|
-
scope_invitations = client.get_invitations_by_scope('team', 'team1')
|
|
124
|
+
# Generate a token for the current user
|
|
125
|
+
token = client.generate_token(user: { id: 'user-123', email: 'user@example.com' })
|
|
126
|
+
|
|
127
|
+
# Pass the token to your frontend component
|
|
128
|
+
|
|
73
129
|
```
|
|
74
130
|
|
|
75
|
-
##
|
|
131
|
+
## Installation
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
gem install vortex-ruby-sdk
|
|
135
|
+
```
|
|
76
136
|
|
|
77
|
-
|
|
137
|
+
## Initialization
|
|
78
138
|
|
|
79
139
|
```ruby
|
|
80
|
-
|
|
81
|
-
token = client.generate_token({ user: { id: 'user-123' } })
|
|
82
|
-
|
|
83
|
-
# Sign full payload with component, scope, and variables
|
|
84
|
-
token = client.generate_token({
|
|
85
|
-
component: 'widget-abc',
|
|
86
|
-
user: { id: 'user-123', name: 'Peter', email: 'peter@example.com' },
|
|
87
|
-
scope: 'workspace_456',
|
|
88
|
-
vars: { company_name: 'Acme' }
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
# Custom expiration (default is 5 minutes)
|
|
92
|
-
payload = { user: { id: 'user-123' } }
|
|
93
|
-
token = client.generate_token(payload, { expires_in: '1h' }) # Supports "5m", "1h", "24h", "7d"
|
|
94
|
-
token = client.generate_token(payload, { expires_in: 3600 }) # Or seconds as integer
|
|
140
|
+
client = Vortex::Client.new(ENV['VORTEX_API_KEY'])
|
|
95
141
|
```
|
|
96
142
|
|
|
97
|
-
|
|
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()`
|
|
98
154
|
|
|
99
|
-
|
|
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:**
|
|
100
158
|
|
|
101
159
|
```ruby
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
include Vortex::Rails::Controller
|
|
105
|
-
|
|
106
|
-
private
|
|
107
|
-
|
|
108
|
-
def authenticate_vortex_user
|
|
109
|
-
# Return user data hash or nil
|
|
110
|
-
admin_scopes = []
|
|
111
|
-
admin_scopes << 'autojoin' if current_user.admin?
|
|
112
|
-
|
|
113
|
-
{
|
|
114
|
-
id: current_user.id,
|
|
115
|
-
email: current_user.email,
|
|
116
|
-
admin_scopes: admin_scopes
|
|
117
|
-
}
|
|
118
|
-
end
|
|
160
|
+
generate_token(payload, options = nil)
|
|
161
|
+
```
|
|
119
162
|
|
|
120
|
-
|
|
121
|
-
# Implement your authorization logic
|
|
122
|
-
case operation
|
|
123
|
-
when 'JWT', 'GET_INVITATIONS'
|
|
124
|
-
true
|
|
125
|
-
when 'REVOKE_INVITATION'
|
|
126
|
-
user[:admin_scopes]&.include?('autojoin')
|
|
127
|
-
else
|
|
128
|
-
false
|
|
129
|
-
end
|
|
130
|
-
end
|
|
163
|
+
**Parameters:**
|
|
131
164
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
136
187
|
```
|
|
137
188
|
|
|
138
|
-
|
|
189
|
+
_Added in v0.8.0_
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
### `get_invitation()`
|
|
194
|
+
|
|
195
|
+
Get a specific invitation by ID
|
|
196
|
+
|
|
197
|
+
**Signature:**
|
|
139
198
|
|
|
140
199
|
```ruby
|
|
141
|
-
|
|
142
|
-
scope '/api/vortex', controller: 'vortex' do
|
|
143
|
-
post 'jwt', action: 'generate_jwt'
|
|
144
|
-
get 'invitations', action: 'get_invitations_by_target'
|
|
145
|
-
get 'invitations/:invitation_id', action: 'get_invitation'
|
|
146
|
-
delete 'invitations/:invitation_id', action: 'revoke_invitation'
|
|
147
|
-
post 'invitations/accept', action: 'accept_invitations'
|
|
148
|
-
get 'invitations/by-scope/:scope_type/:scope', action: 'get_invitations_by_scope'
|
|
149
|
-
delete 'invitations/by-scope/:scope_type/:scope', action: 'delete_invitations_by_scope'
|
|
150
|
-
post 'invitations/:invitation_id/reinvite', action: 'reinvite'
|
|
151
|
-
end
|
|
152
|
-
end
|
|
200
|
+
get_invitation(invitation_id)
|
|
153
201
|
```
|
|
154
202
|
|
|
155
|
-
|
|
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:**
|
|
156
221
|
|
|
157
222
|
```ruby
|
|
158
|
-
|
|
159
|
-
|
|
223
|
+
accept_invitation(invitation_id, user)
|
|
224
|
+
```
|
|
160
225
|
|
|
161
|
-
|
|
162
|
-
register Vortex::Sinatra
|
|
226
|
+
**Parameters:**
|
|
163
227
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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 |
|
|
167
232
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
user_id = request.env['HTTP_X_USER_ID']
|
|
171
|
-
return nil unless user_id
|
|
233
|
+
**Returns:** `String`
|
|
234
|
+
— The accepted invitation result
|
|
172
235
|
|
|
173
|
-
|
|
174
|
-
id: user_id,
|
|
175
|
-
email: 'user@example.com',
|
|
176
|
-
admin_scopes: [] # Optional
|
|
177
|
-
}
|
|
178
|
-
end
|
|
236
|
+
**Example:**
|
|
179
237
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
end
|
|
184
|
-
end
|
|
238
|
+
```ruby
|
|
239
|
+
# user = { email: 'user@example.com', name: 'John Doe' }
|
|
240
|
+
# result = client.accept_invitation('inv-123', user)
|
|
185
241
|
```
|
|
186
242
|
|
|
187
|
-
|
|
243
|
+
_Added in v0.6.0_
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
## All Methods
|
|
248
|
+
|
|
249
|
+
<details>
|
|
250
|
+
<summary>Click to expand full method reference</summary>
|
|
251
|
+
|
|
252
|
+
### `get_invitations_by_target()`
|
|
188
253
|
|
|
189
|
-
|
|
254
|
+
Get invitations by target
|
|
190
255
|
|
|
191
|
-
|
|
256
|
+
**Signature:**
|
|
192
257
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
258
|
+
```ruby
|
|
259
|
+
get_invitations_by_target(target_type, target_value)
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
**Parameters:**
|
|
196
263
|
|
|
197
|
-
|
|
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) |
|
|
198
268
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
- `revoke_invitation(invitation_id)` - Revoke invitation
|
|
202
|
-
- `accept_invitation(invitation_id, user)` - Accept an invitation
|
|
203
|
-
- `get_invitations_by_scope(scope_type, scope)` - Get scope invitations
|
|
204
|
-
- `delete_invitations_by_scope(scope_type, scope)` - Delete scope invitations
|
|
205
|
-
- `reinvite(invitation_id)` - Reinvite user
|
|
206
|
-
- `sync_internal_invitation(creator_id, target_value, action, component_id)` - Sync internal invitation action
|
|
269
|
+
**Returns:** `String`
|
|
270
|
+
— List of invitations
|
|
207
271
|
|
|
208
|
-
|
|
272
|
+
_Added in v0.1.0_
|
|
209
273
|
|
|
210
|
-
|
|
274
|
+
---
|
|
211
275
|
|
|
212
|
-
|
|
213
|
-
- `GET /api/vortex/invitations?targetType=email&targetValue=user@example.com`
|
|
214
|
-
- `GET /api/vortex/invitations/:invitation_id`
|
|
215
|
-
- `DELETE /api/vortex/invitations/:invitation_id`
|
|
216
|
-
- `POST /api/vortex/invitations/accept`
|
|
217
|
-
- `GET /api/vortex/invitations/by-scope/:scope_type/:scope`
|
|
218
|
-
- `DELETE /api/vortex/invitations/by-scope/:scope_type/:scope`
|
|
219
|
-
- `POST /api/vortex/invitations/:invitation_id/reinvite`
|
|
220
|
-
- `POST /api/vortex/invitations/sync-internal-invitation`
|
|
276
|
+
### `revoke_invitation()`
|
|
221
277
|
|
|
222
|
-
|
|
278
|
+
Revoke (delete) an invitation
|
|
223
279
|
|
|
224
|
-
|
|
280
|
+
**Signature:**
|
|
225
281
|
|
|
226
282
|
```ruby
|
|
227
|
-
|
|
228
|
-
result = client.sync_internal_invitation(
|
|
229
|
-
'user-123', # creator_id - The inviter's user ID in your system
|
|
230
|
-
'user-456', # target_value - The invitee's user ID in your system
|
|
231
|
-
'accepted', # action - "accepted" or "declined"
|
|
232
|
-
'component-uuid' # component_id - The widget component UUID
|
|
233
|
-
)
|
|
234
|
-
|
|
235
|
-
puts "Processed: #{result['processed']}"
|
|
236
|
-
puts "Invitation IDs: #{result['invitationIds']}"
|
|
283
|
+
revoke_invitation(invitation_id)
|
|
237
284
|
```
|
|
238
285
|
|
|
239
286
|
**Parameters:**
|
|
240
287
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
- `component_id` (String) — The widget component UUID
|
|
288
|
+
| Name | Type | Required | Description |
|
|
289
|
+
| --------------- | -------- | -------- | --------------------------- |
|
|
290
|
+
| `invitation_id` | `String` | ✓ | The invitation ID to revoke |
|
|
245
291
|
|
|
246
|
-
**
|
|
292
|
+
**Returns:** `String`
|
|
293
|
+
— Success response
|
|
247
294
|
|
|
248
|
-
|
|
249
|
-
- `invitationIds` (Array<String>) — IDs of processed invitations
|
|
295
|
+
_Added in v0.1.0_
|
|
250
296
|
|
|
251
|
-
|
|
297
|
+
---
|
|
252
298
|
|
|
253
|
-
|
|
254
|
-
- Users accept/decline invitations within your application
|
|
255
|
-
- You need to keep Vortex updated with the invitation status
|
|
299
|
+
### `accept_invitations()`
|
|
256
300
|
|
|
257
|
-
|
|
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: '...' }, ...]
|
|
258
302
|
|
|
259
|
-
|
|
303
|
+
**Signature:**
|
|
260
304
|
|
|
261
305
|
```ruby
|
|
262
|
-
|
|
263
|
-
userId: 'user-123',
|
|
264
|
-
userEmail: 'user@example.com',
|
|
265
|
-
adminScopes: ['autojoin'], # Full array included if admin_scopes provided
|
|
266
|
-
expires: 1234567890
|
|
267
|
-
}
|
|
306
|
+
accept_invitations(invitation_ids, user_or_target)
|
|
268
307
|
```
|
|
269
308
|
|
|
270
|
-
|
|
309
|
+
**Parameters:**
|
|
271
310
|
|
|
272
|
-
|
|
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) |
|
|
273
315
|
|
|
274
|
-
|
|
316
|
+
**Returns:** `String`
|
|
317
|
+
— The accepted invitation result
|
|
318
|
+
|
|
319
|
+
**Example:**
|
|
275
320
|
|
|
276
321
|
```ruby
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
)
|
|
284
|
-
rescue Vortex::VortexError => e
|
|
285
|
-
logger.error "Vortex error: #{e.message}"
|
|
286
|
-
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)
|
|
287
327
|
```
|
|
288
328
|
|
|
289
|
-
|
|
329
|
+
_Added in v0.1.0_
|
|
290
330
|
|
|
291
|
-
|
|
331
|
+
---
|
|
292
332
|
|
|
293
|
-
|
|
333
|
+
### `get_invitations_by_scope()`
|
|
294
334
|
|
|
295
|
-
|
|
335
|
+
Get invitations by group
|
|
296
336
|
|
|
297
|
-
|
|
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 |
|
|
298
349
|
|
|
299
|
-
|
|
350
|
+
**Returns:** `String`
|
|
351
|
+
— List of invitations for the group
|
|
352
|
+
|
|
353
|
+
_Added in v0.4.0_
|
|
354
|
+
|
|
355
|
+
---
|
|
356
|
+
|
|
357
|
+
### `delete_invitations_by_scope()`
|
|
358
|
+
|
|
359
|
+
Delete invitations by group
|
|
360
|
+
|
|
361
|
+
**Signature:**
|
|
300
362
|
|
|
301
363
|
```ruby
|
|
302
|
-
|
|
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
|
+
---
|
|
380
|
+
|
|
381
|
+
### `reinvite()`
|
|
382
|
+
|
|
383
|
+
Reinvite a user
|
|
303
384
|
|
|
304
|
-
|
|
385
|
+
**Signature:**
|
|
305
386
|
|
|
306
|
-
|
|
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
|
|
307
702
|
class WebhooksController < ApplicationController
|
|
308
703
|
skip_before_action :verify_authenticity_token
|
|
309
704
|
|
|
310
|
-
def
|
|
311
|
-
|
|
705
|
+
def vortex
|
|
706
|
+
webhooks = Vortex::Webhooks.new(ENV['VORTEX_WEBHOOK_SECRET'])
|
|
707
|
+
|
|
708
|
+
payload = request.raw_post
|
|
312
709
|
signature = request.headers['X-Vortex-Signature']
|
|
313
710
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
head :bad_request
|
|
318
|
-
return
|
|
711
|
+
# Verify the signature
|
|
712
|
+
unless webhooks.verify_signature(payload, signature)
|
|
713
|
+
return render json: { error: 'Invalid signature' }, status: 400
|
|
319
714
|
end
|
|
320
715
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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']}"
|
|
326
726
|
end
|
|
327
727
|
|
|
328
|
-
|
|
728
|
+
render json: { received: true }
|
|
329
729
|
end
|
|
330
730
|
end
|
|
731
|
+
|
|
331
732
|
```
|
|
332
733
|
|
|
333
|
-
###
|
|
734
|
+
### Common Use Cases
|
|
334
735
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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`.
|
|
340
775
|
|
|
341
|
-
|
|
776
|
+
| Error | Description |
|
|
777
|
+
| ------------- | ---------------------------------------------------------------------------------------- |
|
|
778
|
+
| `VortexError` | Raised for validation errors (e.g., missing API key, invalid parameters) or API failures |
|
|
342
779
|
|
|
343
|
-
|
|
780
|
+
---
|
|
344
781
|
|
|
345
|
-
|
|
782
|
+
<!-- Generated from SDK v1.18.0 manifest -->
|
data/lib/vortex/client.rb
CHANGED
|
@@ -122,7 +122,7 @@ module Vortex
|
|
|
122
122
|
|
|
123
123
|
public
|
|
124
124
|
|
|
125
|
-
def generate_jwt(params)
|
|
125
|
+
def generate_jwt(params, options = {})
|
|
126
126
|
user = params[:user]
|
|
127
127
|
attributes = params[:attributes]
|
|
128
128
|
|
|
@@ -138,7 +138,9 @@ module Vortex
|
|
|
138
138
|
# Convert to UUID string format (same as uuidStringify in Node.js)
|
|
139
139
|
id = format_uuid(decoded_bytes)
|
|
140
140
|
|
|
141
|
-
|
|
141
|
+
raw_expires = options[:expires_in] || options[:expiresIn]
|
|
142
|
+
expires_in_seconds = raw_expires ? parse_expires_in(raw_expires) : 2592000 # 30 days
|
|
143
|
+
expires = Time.now.to_i + expires_in_seconds
|
|
142
144
|
|
|
143
145
|
# Step 1: Derive signing key from API key + ID (same as Node.js)
|
|
144
146
|
signing_key = OpenSSL::HMAC.digest('sha256', key, id)
|
|
@@ -207,7 +209,7 @@ module Vortex
|
|
|
207
209
|
# @param payload [Hash] Data to sign (user, component, scope, vars, etc.)
|
|
208
210
|
# At minimum, include user[:id] for secure invitation attribution.
|
|
209
211
|
# @param options [Hash] Optional configuration
|
|
210
|
-
# @option options [String, Integer] :expires_in Expiration time (default:
|
|
212
|
+
# @option options [String, Integer] :expires_in Expiration time (default: 30 days)
|
|
211
213
|
# Can be a duration string ("5m", "1h", "24h", "7d") or seconds as integer.
|
|
212
214
|
# @return [String] Signed JWT token
|
|
213
215
|
# @raise [VortexError] If API key format is invalid or token generation fails
|
|
@@ -223,7 +225,7 @@ module Vortex
|
|
|
223
225
|
# vars: { company_name: 'Acme' }
|
|
224
226
|
# })
|
|
225
227
|
#
|
|
226
|
-
# @example Custom expiration (default is
|
|
228
|
+
# @example Custom expiration (default is 30 days)
|
|
227
229
|
# token = client.generate_token(payload, { expires_in: '1h' })
|
|
228
230
|
# token = client.generate_token(payload, { expires_in: 3600 }) # seconds
|
|
229
231
|
def generate_token(payload, options = nil)
|
|
@@ -241,7 +243,7 @@ module Vortex
|
|
|
241
243
|
end
|
|
242
244
|
|
|
243
245
|
# Parse expiration
|
|
244
|
-
expires_in_seconds =
|
|
246
|
+
expires_in_seconds = 30 * 24 * 60 * 60 # Default: 30 days
|
|
245
247
|
if options
|
|
246
248
|
options = symbolize_keys(options)
|
|
247
249
|
raw_expires = options[:expires_in] || options[:expiresIn]
|
|
@@ -310,7 +312,7 @@ module Vortex
|
|
|
310
312
|
# @return [Integer] Expiration time in seconds
|
|
311
313
|
# @raise [VortexError] If format is invalid
|
|
312
314
|
def parse_expires_in(expires_in)
|
|
313
|
-
return
|
|
315
|
+
return 30 * 24 * 60 * 60 if expires_in.nil?
|
|
314
316
|
|
|
315
317
|
if expires_in.is_a?(Integer)
|
|
316
318
|
raise VortexError, "Invalid expires_in value: #{expires_in}. Numeric expires_in must be a positive integer number of seconds." if expires_in <= 0
|
|
@@ -684,7 +686,7 @@ module Vortex
|
|
|
684
686
|
# @param scope [String] The scope identifier (customer's group ID)
|
|
685
687
|
# @param scope_type [String] The type of scope (e.g., "organization", "team")
|
|
686
688
|
# @param domains [Array<String>] Array of domains to configure for autojoin
|
|
687
|
-
# @param
|
|
689
|
+
# @param component_id [String] The component ID
|
|
688
690
|
# @param scope_name [String, nil] Optional display name for the scope
|
|
689
691
|
# @param metadata [Hash, nil] Optional metadata to attach to the invitation
|
|
690
692
|
# @return [Hash] Response with :autojoin_domains array and :invitation
|
|
@@ -695,20 +697,20 @@ module Vortex
|
|
|
695
697
|
# 'acme-org',
|
|
696
698
|
# 'organization',
|
|
697
699
|
# ['acme.com', 'acme.org'],
|
|
698
|
-
# '
|
|
700
|
+
# 'component-123',
|
|
699
701
|
# 'Acme Corporation'
|
|
700
702
|
# )
|
|
701
|
-
def configure_autojoin(scope, scope_type, domains,
|
|
703
|
+
def configure_autojoin(scope, scope_type, domains, component_id, scope_name = nil, metadata = nil)
|
|
702
704
|
raise VortexError, 'scope is required' if scope.nil? || scope.empty?
|
|
703
705
|
raise VortexError, 'scope_type is required' if scope_type.nil? || scope_type.empty?
|
|
704
|
-
raise VortexError, '
|
|
706
|
+
raise VortexError, 'component_id is required' if component_id.nil? || component_id.empty?
|
|
705
707
|
raise VortexError, 'domains must be an array' unless domains.is_a?(Array)
|
|
706
708
|
|
|
707
709
|
body = {
|
|
708
710
|
scope: scope,
|
|
709
711
|
scopeType: scope_type,
|
|
710
712
|
domains: domains,
|
|
711
|
-
|
|
713
|
+
componentId: component_id
|
|
712
714
|
}
|
|
713
715
|
|
|
714
716
|
body[:scopeName] = scope_name if scope_name
|
data/lib/vortex/types.rb
CHANGED
|
@@ -81,6 +81,9 @@ module Vortex
|
|
|
81
81
|
id: String,
|
|
82
82
|
accountId: String,
|
|
83
83
|
clickThroughs: Integer,
|
|
84
|
+
# Invitation form data submitted by the user, including email addresses of invitees and the values of any custom fields.
|
|
85
|
+
formSubmissionData: Hash,
|
|
86
|
+
# @deprecated Use formSubmissionData instead. Contains the same data.
|
|
84
87
|
configurationAttributes: Hash,
|
|
85
88
|
attributes: Hash,
|
|
86
89
|
createdAt: String,
|
|
@@ -94,9 +97,8 @@ module Vortex
|
|
|
94
97
|
target: Array, # of INVITATION_TARGET structures
|
|
95
98
|
views: Integer,
|
|
96
99
|
widgetConfigurationId: String,
|
|
97
|
-
projectId: String,
|
|
98
100
|
groups: Array, # of INVITATION_GROUP structures
|
|
99
|
-
accepts: Array, # of acceptance structures
|
|
101
|
+
accepts: Array, # of acceptance structures (optional)
|
|
100
102
|
expired: :boolean,
|
|
101
103
|
expires: String # ISO 8601 timestamp (optional)
|
|
102
104
|
}.freeze
|
data/lib/vortex/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vortex-ruby-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.18.0.pre.20260427181620
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vortex Software
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-04-
|
|
11
|
+
date: 2026-04-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: faraday
|
|
@@ -182,9 +182,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
182
182
|
version: 3.0.0
|
|
183
183
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
184
184
|
requirements:
|
|
185
|
-
- - "
|
|
185
|
+
- - ">"
|
|
186
186
|
- !ruby/object:Gem::Version
|
|
187
|
-
version:
|
|
187
|
+
version: 1.3.1
|
|
188
188
|
requirements: []
|
|
189
189
|
rubygems_version: 3.4.19
|
|
190
190
|
signing_key:
|