Skip to main content
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.

Installation

cargo add vortex-sdk

Quick Start

Generate a secure token for Vortex components
use vortex_sdk::{VortexClient, GenerateTokenPayload, TokenUser};

let client = VortexClient::new(std::env::var("VORTEX_API_KEY")?);

let payload = GenerateTokenPayload {
    user: Some(TokenUser { id: "user-123".into(), email: Some("user@example.com".into()), ..Default::default() }),
    ..Default::default()
};

let token = client.generate_token(payload, None)?;

Integration Flow

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.
1

Install the backend SDK

Add this SDK to your Rust project
cargo add vortex-sdk
2

Initialize the client

Create a Vortex client with your API key (keep this on the server!)
use vortex_sdk::VortexClient;

let client = VortexClient::new(std::env::var("VORTEX_API_KEY")?);
3

Generate a token for the current user

When a user loads a page with a Vortex component, generate a signed token on your server
let payload = GenerateTokenPayload {
user: Some(TokenUser { id: user_id.into(), ..Default::default() }),
..Default::default()
};
let token = client.generate_token(payload, None)?;
4

Pass the token to your frontend

Include the token in your response
Json(json!({ "vortex_token": token }))
5

Render a Vortex component with the token

Use the React/Angular/Web Component with the token
import { VortexInvite } from "@teamvortexsoftware/vortex-react";

<VortexInvite token={vortexToken} />
6

Vortex handles the rest

The component securely communicates with Vortex servers, displays the invitation UI, sends emails/SMS, tracks conversions, and reports analytics

Methods

Core Methods

generate_token()

Generate a signed token for use with Vortex widgets
fn generate_token(&self, payload: &GenerateTokenPayload, options: Option<&GenerateTokenOptions>) -> Result<String, VortexError>
Parameters:
NameTypeRequiredDescription
payload&GenerateTokenPayloadData to sign (user, component, scope, vars, etc.)
optionsOption&lt;&GenerateTokenOptions&gt;Optional configuration (expires_in)
Returns: Result&lt;String, VortexError&gt;

get_invitation()

Get a specific invitation by ID
async fn get_invitation(&self, invitation_id: &str) -> Result<Invitation, VortexError>
Parameters:
NameTypeRequiredDescription
invitation_id&strThe invitation ID
Returns: Result&lt;Invitation, VortexError&gt;

accept_invitation()

Accept a single invitation (recommended method)
async fn accept_invitation(&self, invitation_id: &str, user: crate::types::AcceptUser) -> Result<Invitation, VortexError>
Parameters:
NameTypeRequiredDescription
invitation_id&strSingle invitation ID to accept
usercrate::types::AcceptUserUser object with email and/or phone
Returns: Result&lt;Invitation, VortexError&gt;
use vortex_sdk::{VortexClient, AcceptUser};

let client = VortexClient::new("VRTX.key.secret".to_string());
let user = AcceptUser::new().with_email("user@example.com");
let result = client.accept_invitation("inv-123", user).await;

get_invitations_by_target()

Get invitations by target (email or sms)
async fn get_invitations_by_target(&self, target_type: &str, target_value: &str) -> Result<Vec<Invitation>, VortexError>
Parameters:
NameTypeRequiredDescription
target_type&strType of target (email, phone)
target_value&strThe target value
Returns: Result&lt;Vec&lt;Invitation&gt;, VortexError&gt;

revoke_invitation()

Revoke (delete) an invitation
async fn revoke_invitation(&self, invitation_id: &str) -> Result<(), VortexError>
Parameters:
NameTypeRequiredDescription
invitation_id&strThe invitation ID to revoke
Returns: Result&lt;(), VortexError&gt;

accept_invitations()

Accept multiple invitations
async fn accept_invitations(&self, invitation_ids: Vec<String>, param: impl Into<crate::types::AcceptInvitationParam>) -> Result<Invitation, VortexError>
Parameters:
NameTypeRequiredDescription
invitation_idsVec&lt;String&gt;Vector of invitation IDs to accept
paramimpl Into&lt;crate::types::AcceptInvitationParam&gt;User data (preferred) or legacy target format
Returns: Result&lt;Invitation, VortexError&gt;

delete_invitations_by_scope()

Delete all invitations for a specific scope
async fn delete_invitations_by_scope(&self, scope_type: &str, scope: &str) -> Result<(), VortexError>
Parameters:
NameTypeRequiredDescription
scope_type&strThe scope type (organization, team, etc.)
scope&strThe scope identifier
Returns: Result&lt;(), VortexError&gt;

get_invitations_by_scope()

Get all invitations for a specific scope
async fn get_invitations_by_scope(&self, scope_type: &str, scope: &str) -> Result<Vec<Invitation>, VortexError>
Parameters:
NameTypeRequiredDescription
scope_type&strThe scope type (organization, team, etc.)
scope&strThe scope identifier
Returns: Result&lt;Vec&lt;Invitation&gt;, VortexError&gt;

reinvite()

Reinvite a user (send invitation again)
async fn reinvite(&self, invitation_id: &str) -> Result<Invitation, VortexError>
Parameters:
NameTypeRequiredDescription
invitation_id&strThe invitation ID to reinvite
Returns: Result&lt;Invitation, VortexError&gt;

create_invitation()

Create an invitation from your backend This method allows you to create invitations programmatically using your API key, without requiring a user JWT token. Useful for server-side invitation creation, such as “People You May Know” flows or admin-initiated invitations. - email: Send an email invitation - sms: Create an SMS invitation (short link returned for you to send) - internal: Create an internal invitation for PYMK flows (no email sent)
async fn create_invitation(&self, request: CreateInvitationRequest) -> Result<CreateInvitationResponse, VortexError>
Parameters:
NameTypeRequiredDescription
requestCreateInvitationRequest
Returns: Result&lt;CreateInvitationResponse, VortexError&gt;
use vortex_sdk::{VortexClient, CreateInvitationRequest, CreateInvitationTarget, Inviter, CreateInvitationScope};

async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = VortexClient::new("VRTX.xxx.yyy".to_string());

// Create an email invitation
let request = CreateInvitationRequest::new(
"widget-config-123",
CreateInvitationTarget::email("invitee@example.com"),
Inviter::new("user-456")
.with_email("inviter@example.com")
.with_user_name("John Doe"),
)
.with_groups(vec![
CreateInvitationScope::new("team", "team-789", "Engineering"),
]);

let result = client.create_invitation(request).await?;

// Create an internal invitation (PYMK flow - no email sent)
let request = CreateInvitationRequest::new(
"widget-config-123",
CreateInvitationTarget::internal("internal-user-abc"),
Inviter::new("user-456"),
)
.with_source("pymk");

let result = client.create_invitation(request).await?;
Ok(())
}

get_autojoin_domains()

Get autojoin domains configured for a specific scope
async fn get_autojoin_domains(&self, scope_type: &str, scope: &str) -> Result<AutojoinDomainsResponse, VortexError>
Parameters:
NameTypeRequiredDescription
scope_type&strThe type of scope (e.g., “organization”, “team”, “project”)
scope&strThe scope identifier (customer’s group ID)
Returns: Result&lt;AutojoinDomainsResponse, VortexError&gt;

sync_internal_invitation()

Configure autojoin domains for a specific scope
async fn sync_internal_invitation(&self, request: &SyncInternalInvitationRequest) -> Result<SyncInternalInvitationResponse, VortexError>
Parameters:
NameTypeRequiredDescription
request&SyncInternalInvitationRequestThe configure autojoin request
Returns: Result&lt;SyncInternalInvitationResponse, VortexError&gt;
use vortex_sdk::{VortexClient, SyncInternalInvitationRequest};

async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = VortexClient::new("VRTX.xxx.yyy".to_string());
let request = SyncInternalInvitationRequest::new(
"user-123", "user-456", "accepted", "component-uuid",
);
let result = client.sync_internal_invitation(&request).await?;
println!("Processed {} invitations", result.processed);
Ok(())
}

Types

GenerateTokenPayload

Payload for generate_token() - used to generate secure tokens for Vortex components
FieldTypeRequiredDescription
userOption&lt;TokenUser&gt;The authenticated user who will be using the Vortex component
componentOption&lt;String&gt;Component ID to generate token for (from your Vortex dashboard)
scopeOption&lt;String&gt;Scope identifier to restrict invitations (format: “scopeType:scopeId”)
varsOption&lt;HashMap&lt;String, Value&gt;&gt;Custom variables to pass to the component for template rendering

TokenUser

User data for token generation - represents the authenticated user sending invitations
FieldTypeRequiredDescription
idStringUnique identifier for the user in your system. Used to attribute invitations.
emailOption&lt;String&gt;User’s email address. Used for reply-to in invitation emails.
nameOption&lt;String&gt;Display name shown to invitation recipients (e.g., “John invited you”)
avatar_urlOption&lt;String&gt;URL to user’s avatar image. Displayed in invitation emails and widgets.
admin_scopesOption&lt;Vec&lt;String&gt;&gt;List of scope IDs where this user has admin privileges
allowed_email_domainsOption&lt;Vec&lt;String&gt;&gt;Restrict invitations to specific email domains (e.g., [“acme.com”])

AcceptUser

User data for accepting invitations - identifies who accepted the invitation
FieldTypeRequiredDescription
emailOption&lt;String&gt;Email address of the accepting user. At least one of email or phone is required.
phoneOption&lt;String&gt;Phone number with country code. At least one of email or phone is required.
nameOption&lt;String&gt;Display name of the accepting user (shown in notifications to inviter)
is_existingOption&lt;bool&gt;Whether user was already registered. Some(true)=existing, Some(false)=new signup, None=unknown.

CreateInvitationTarget

Target specification when creating an invitation - where to send the invite
FieldTypeRequiredDescription
target_typeStringDelivery channel: “email”, “phone”, “share”, or “internal”
valueStringTarget address: email address, phone number with country code, or internal user ID
nameOption&lt;String&gt;Display name of the recipient (used in email greetings)

CreateInvitationScope

Scope specification when creating an invitation - what group/team to invite into
FieldTypeRequiredDescription
scope_typeStringScope type (e.g., “team”, “organization”, “workspace”)
group_idStringYour internal identifier for this scope/group
nameStringDisplay name for the scope (shown in invitation emails)

Identifier

Email or phone identifier for looking up users
FieldTypeRequiredDescription
identifier_typeStringIdentifier type: “email” or “phone”
valueStringThe email address or phone number (with country code for phone)

ConfigureAutojoinRequest

Request to configure autojoin domains for a scope
FieldTypeRequiredDescription
scope_typeStringType of scope (e.g., “team”, “workspace”)
scope_idStringYour internal identifier for the scope
domainsVec&lt;String&gt;List of email domains to enable autojoin for (e.g., [“acme.com”])

SyncInternalInvitationRequest

Request to sync an internal invitation (for tracking invitations made outside Vortex)
FieldTypeRequiredDescription
inviter_idStringYour internal user ID for the person who sent the invitation
targetCreateInvitationTargetThe invitation recipient
scopesOption&lt;Vec&lt;CreateInvitationScope&gt;&gt;Scopes/groups the invitation grants access to

InvitationResult

Complete invitation details as returned by the Vortex API
FieldTypeRequiredDescription
idStringUnique identifier for this invitation
account_idStringYour Vortex account ID
click_throughsi32Number of times the invitation link was clicked
created_atStringISO 8601 timestamp when the invitation was created
deactivatedboolWhether this invitation has been revoked or expired
delivery_counti32Number of times the invitation was sent (including reminders)
delivery_typesVec&lt;String&gt;Channels used to deliver: “email”, “phone”, “share”, “internal”
foreign_creator_idStringYour internal user ID for the person who created this invitation
invitation_typeStringType: “single_use”, “multi_use”, or “autojoin”
statusStringCurrent status: queued, sending, sent, delivered, accepted, shared
targetVec&lt;InvitationTarget&gt;List of invitation recipients with their contact info and status
viewsi32Number of times the invitation page was viewed
groupsVec&lt;InvitationScope&gt;Scopes (teams/orgs) this invitation grants access to
expiredboolWhether this invitation has passed its expiration date
expiresOption&lt;String&gt;ISO 8601 timestamp when this invitation expires
inviterOption&lt;Inviter&gt;Information about who sent the invitation

InvitationTarget

Target recipient of an invitation (from API response)
FieldTypeRequiredDescription
target_typeStringDelivery channel: “email”, “phone”, “share”, or “internal”
valueStringTarget address: email, phone number with country code, or share link ID
nameOption&lt;String&gt;Display name of the recipient
avatar_urlOption&lt;String&gt;Avatar URL for the recipient
statusOption&lt;String&gt;Delivery status for this specific target

InvitationScope

Scope/group that the invitation grants access to (from API response)
FieldTypeRequiredDescription
idStringVortex internal UUID for this scope record
account_idStringYour Vortex account ID
group_idStringYour internal scope/group identifier
scope_typeStringScope type (e.g., “team”, “organization”, “workspace”)
nameStringDisplay name for the scope
created_atStringISO 8601 timestamp when the scope was created

InvitationAcceptance

Details about an invitation acceptance event
FieldTypeRequiredDescription
idStringUnique identifier for this acceptance record
invitation_idStringID of the invitation that was accepted
emailOption&lt;String&gt;Email of the user who accepted
phoneOption&lt;String&gt;Phone of the user who accepted
nameOption&lt;String&gt;Name of the user who accepted
is_existingOption&lt;bool&gt;Whether the user already had an account
created_atStringISO 8601 timestamp when the acceptance occurred

Inviter

Information about the user who sent an invitation
FieldTypeRequiredDescription
idStringYour internal user ID for the inviter
emailOption&lt;String&gt;Email address of the inviter
nameOption&lt;String&gt;Display name of the inviter
avatar_urlOption&lt;String&gt;Avatar URL of the inviter

AutojoinDomain

Autojoin domain configuration - users with matching email domains automatically join
FieldTypeRequiredDescription
idStringUnique identifier for this autojoin configuration
domainStringEmail domain that triggers autojoin (e.g., “acme.com”)

AutojoinDomainsResponse

Response from get_autojoin_domains()
FieldTypeRequiredDescription
domainsVec&lt;AutojoinDomain&gt;List of configured autojoin domains

SyncInternalInvitationResponse

Response from sync_internal_invitation()
FieldTypeRequiredDescription
invitationInvitationResultThe created or updated invitation
createdbooltrue if a new invitation was created, false if existing was updated

VortexWebhookEvent

Webhook event payload delivered to your endpoint
FieldTypeRequiredDescription
idStringUnique identifier for this webhook delivery
event_typeStringEvent type (e.g., “invitation.accepted”, “member.created”)
timestampStringISO 8601 timestamp when the event occurred
dataValueEvent-specific payload data (serde_json::Value)

Webhooks

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.

Setup

  1. Go to your Vortex dashboard → Integrations → Webhooks tab
  2. Click “Add Webhook”
  3. Enter your endpoint URL (must be HTTPS in production)
  4. Copy the signing secret — you’ll use this to verify webhook signatures
  5. Select which events you want to receive

Example

Axum webhook handler
use axum::{extract::State, http::HeaderMap, Json};
use vortex_sdk::VortexWebhooks;

async fn handle_webhook(
    State(webhooks): State<VortexWebhooks>,
    headers: HeaderMap,
    body: String,
) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
    let signature = headers
        .get("X-Vortex-Signature")
        .and_then(|v| v.to_str().ok())
        .ok_or((StatusCode::BAD_REQUEST, "Missing signature".into()))?;
    
    // Verify the signature
    if !webhooks.verify_signature(&body, signature) {
        return Err((StatusCode::BAD_REQUEST, "Invalid signature".into()));
    }
    
    // Parse the event
    let event = webhooks.parse_event(&body)?;
    
    match event.event_type.as_str() {
        "invitation.accepted" => {
            // User accepted an invitation — activate their account
            println!("Accepted: {:?}", event.data);
        }
        "member.created" => {
            // New member joined via invitation
            println!("New member: {:?}", event.data);
        }
        _ => {}
    }
    
    Ok(Json(serde_json::json!({ "received": true })))
}

Events

EventDescription
invitation.createdA new invitation was created
invitation.acceptedAn invitation was accepted by the recipient
invitation.deactivatedAn invitation was deactivated (revoked or expired)
invitation.email.deliveredInvitation email was successfully delivered
invitation.email.bouncedInvitation email bounced (invalid address)
invitation.email.openedRecipient opened the invitation email
invitation.link.clickedRecipient clicked the invitation link
invitation.reminder.sentA reminder email was sent for a pending invitation
member.createdA new member was created from an accepted invitation
group.member.addedA member was added to a scope/group
deployment.createdA new deployment configuration was created
deployment.deactivatedA deployment was deactivated
abtest.startedAn A/B test was started
abtest.winner_declaredAn A/B test winner was declared
email.complainedRecipient marked the email as spam

Use Cases

  • Activate users on acceptance — When invitation.accepted fires, mark the user as active in your database and trigger your onboarding flow.
  • Track invitation performance — Monitor email.delivered, email.opened, and link.clicked events to measure invitation funnel metrics.
  • Sync team membership — Use member.created and group.member.added to keep your internal membership records in sync.
  • Alert on delivery issues — Watch for email.bounced events to proactively reach out via alternative channels.

Error Handling

ErrorDescription
VortexError::WebhookSignatureInvalidReturned when webhook signature verification fails. Check that you are using the raw request body and the correct signing secret.
VortexErrorGeneral error type for validation errors (e.g., missing API key, invalid parameters)

Resources