Skip to main content

Overview

A key benefit of Vortex is that updates to your invitation flows generally don’t require any involvement from developers. Developers do play an important role though, and you’ll need an hour or so of their time to handle the initial implementation. The work will touch both your frontend and backend services. A typical Vortex installation involves the following steps:
  1. Install a Vortex language-specific SDK appropriate for your backend service.
  2. Create a JSON Web Token (JWT) to enable secure communication between your service and Vortex.
  3. Add a snippet of code to your client to render the invitation form.
  4. Make two API calls that bookend your registration process, one prior to registration to get the invitation details and one after registration to mark the invitation as accepted.
When you create your first invitation flow in Vortex you’ll be taken to the Installation tab, which will step you through the installation process. Once the initial implementation is done developers are largely no longer needed. Changes can be made to your invitation form and your email templates without requiring any code changes. There are, however, some changes that will require a small amount of development effort.
  • If a new template variable is added to your email templates, the templateVariables parameter in your client code snippet will need to be updated to include it.
  • If the Google contacts import is enabled, you’ll need to create a Google application and include a client ID in the code snippet.
  • If Autojoin is enabled, you’ll need to make an additional API call during your registration flow and add an interface for your customers’ admins to manage their list of allowed domains.
Vortex will call out these changes via a badge on the Installation tab in your invitation flow.

JSON Web Tokens

JSON web token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. JWTs can be signed, which means you can be certain that the senders are who they say they are. Additionally, the structure of a JWT allows you to verify that the content hasn’t been tampered with. JWTs consist of three parts - the header, the payload and the signature. While it’s up to you to determine when you’ll generate the JWT, ideally you would generate it on-demand when the invitation widget is rendered. This limits the amount of information that you’ll need to include in the payload. The Install Backend step on the Installation tab in your invitation flow will guide you on how to create a JWT using one of Vortex’s SDKs. However, if your language isn’t supported you use the steps below.
The following steps outline how to create a valid header, payload and signature.
1

Derive the signing key from the API key itself

import { stringify as uuidStringify } from 'uuid';
import crypto from 'node:crypto';

const [prefix, encodedId, key] = apiKey.split('.'); // prefix is just VRTX
const id = uuidStringify(Buffer.from(encodedId, 'base64url'));
const signingKey = crypto.createHmac('sha256', key)
  .update(id)
  .digest(); // <- raw Buffer
2

Create the header and payload

The header has the following type:
type header {
	alg: string;
	typ: string;
	kid: string;
}
The payload contains identifying information about the current user (i.e., the inviter), and includes the following required fields.
type payload {
	userId: string;
    userEmail: string;
    userName: string;
    userAvatarUrl: string;
    adminScopes?: string[];
}
The JWT payload consists of:
  • userId- Your internal ID for the current user.
  • userEmail - The current user’s email address.
  • userName - The current user’s full name. Optional.
  • userAvatarUrl - The URL to the current user’s avatar. Optional.
  • adminScopes - Identifies capabilities that are generally limited to people with admin-type priviledges. Currently the only option is “autojoin”, which allows the given user to enable the Autojoin feature.
const userId = ‘55bab438-0492-4dd6-868b-fc93b5b06e32’; // should be the id of the user in your service
const userEmail =foo@bar.com’;
const header = {
    iat: Date.now(),
    alg: 'HS256',
    typ: 'JWT',
    kid: id
};
const payload = {
    userId,
    userEmail,
    userName,
    userAvatarUrl,
    adminScopes,
};
3

Base64 url encode the header and payload

const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
4

Generate the signature and create the JWT

The signature is a string that verifies the integrity of the data in the payload between Vortex and your service.
const toSign = `${headerB64}.${payloadB64}`;
const signature = Buffer.from(
	crypto.createHmac('sha256', signingKey).update(toSign).digest()
).toString('base64url');
const token = `${toSign}.${signature}`;
5

Use the JWT when rendering an invitation widget

Be sure to not expose your API key. You can either embed the JWT as part of the page sent from your backend or even create an endpoint to fetch it on demand. However you present the JWT to your frontend code, ensure the API key is not exposed with it. Also ensure that the JWT you expose to your user is the JWT that identifies the user that will use it.

Client Code Parameters

The Install Client step of the Installation tab provides details on the minimal set of parameters you’ll need to pass along via the code snippet. Although componentId and jwt are technically the only required parameters,scope is effectively required for most customers and there are other parameters detailed below that you will more than likely need to populate.
<VortexInvite
  componentId="ad968b65-f738-4b3b-957b-c3599773d09a"
  jwt=""
  scope=""
/>

componentId

required An invitation flow’s ID can be found on the Installation tab, in the code snippet in the Install Client step.
componentId="aa9ebe43-e565-4299-b2b9-5d01018506ac"

jwt

required Use the JWT generated per the instructions in the JSON Web Tokens section.

scope

required for most customers scope is the internal identifier of the grouping to which the invitation applies. Although some services have invitations that simply establish connections between users, like a social network, most other services, especially B2B services, have invitations that are specific to some type of grouping of users. For example, on Slack and Trello you get invited to a workspace. On Airbnb you get invited to a trip. And if you play fantasy sports, you get invited to a league. If Slack were using Vortex, scope would be the internal ID of the workspace.
scope="f1234567-89ab-cdef-0123-456789abcdef"
These groupings are also what defines the uniquess of an invitation. For example, [email protected] could be invited to three different Slack workspaces. If your service doesn’t have any groupings of users, you do not need to use the scope parameter.

scopeType

Your service may have multiple groupings. For example, Trello has workspaces, but also has the concept of boards that are owned by a workspace. People can be invited to a workspace, but they can also be invited directly to a board. If Trello were using Vortex, scope would represent a workspace ID for invitations to a workspace, or a board ID for invitations to a board. Unless Trello’s workspace and board IDs were of a format such that it was inherently obvious which type of entity they applied to, Trello would use the scopeType parameter, setting it to either “workspace” or “board” as appropriate.
scopeType="workspace"

userEmailsInGroup

If the number of members in a group (as defined by invitationScope) isn’t large, you can send them all via this parameter so that Vortex can validate that the person being invited isn’t already a member. If the number of members is too large to pass all of them in here, use the subsequent emailValidationFunction parameter instead.
userEmailsInGrouping={["[email protected]", "[email protected]", "[email protected]"]}

emailValidationFunction

For large groupings, where sending all members in userEmailsInGrouping isn’t practical, you can provide your own validation function. For example:
const emailValidationFunction = (email: string): boolean | null => {
  return email.includes('@');
};
or
const emailValidationFunction = async (emails: string[]): Promise<{ 
    isValid: boolean, 
    invalidEmails?: string[], 
    errorMessage?: string }> => {
  const result = await someAsyncCheck(email);
  return result;
};
And then this would be included in your client code snippet as follows:
<VortexInvite
          componentId={widgetID}
          isLoading={jwtLoading}
          jwt={jwt}
          emailValidationFunction={emailValidationFunction}
/>
If you return the optional errorMessage parameter it will be displayed in the invitation form when the user enters an email address that is already a member. Otherwise a default message will be displayed.

templateVariables

Template variables allow you to pass in information to be displayed in your invitation emails. For example, you might want to refer to the name of the inviter, the name of a workspace, or even the current number of members already in the workspace. For every variable added to your templates, you’ll need to pass in the values on this parameter.
templateVariables={{ inviter_name:"John Doe", workspace_name: "Acme", member_count: "26" }}

metadata

The metadata field can be used to pass information, in JSON format, along with the invitation that will then be returned to you when you fetch the invitation details during the acceptance flow. The basic information about the inviter and invitee is automatically included, but you may want to capture additional information. For example, say you have an Invite button that is accessible from multiple pages on your site. You might want to include the name of the referring page in the metadata in support of your analytics. When sending data via this method, you are responsible for verifying the integrity of the data when it is returned to you. In deciding what information to send, also consider that the information is being sent in plain text.

googleAppClientId

If your web/desktop invitation flow includes the Google contact import, then you’ll need to pass in the Client ID for your Google app. See instructions for creating your app.

googleIosClientId

If your mobile invitation flow includes the Google contact import, then you’ll need to pass in the Client ID for your Google app. See instructions for creating your app.

onInvite

When passing an onInvite function, you are able to capture the invitations created by your users. For example, if your service treats invitees as pending members for the purposes of email notifications and other functionality, you would want to know the email addresses and custom-field values for the invitees so that you could add them to your users table. If your service doesn’t care at all about invitees until they’ve accepted an invitation, then you don’t need to worry about this. This data format varies based on your invitation flow’s configuration.
import type { OnInviteData } from '@teamvortexsoftware/vortex-react';

const onInvite = (invitations: Invitation[]) => {
	console.log("Invitations:", data);
	processData(data);
}

<VortexInvite
          componentId={widgetID}
          isLoading={jwtLoading}
          jwt={jwt}
          onInvite={onInvite}
/>
Here is an example of the data passed to your registered onInvite function:
[{
acceptedAt: null,
​​	accountId: "43310069-8b56-4294-a0a6-79d910404d39",
​​	attributes: null,
​​	clickThroughs: 0,
​​	configurationAttributes: {
​​​		emails: {
​​​​			role: "email",
​​​​			type: "email",
​​​​			value: "[email protected]"
		}
	},
​​	createdAt: "2025-08-25T18:55:50.551Z",
​​	deactivated: false,
​​	deliveryCount: 0,
​​	deliveryTypes: [
​​​		"email"
	],
	deploymentId: "8a95daf5-76ec-438c-ae34-b91b61d98f21",
​​	environmentId: "df3d007d-2716-41fc-ad32-5477230847b1",
​​	firstClickThrough: null,
​​	foreignCreatorId: "e6453ae2-6ca8-44f4-b0b5-b72f1f642e5e",
​​	id: "65b3c403-c03a-4baa-8eaa-20a40af31dd4",
​​	invitationType: "single_use",
​​	lastClickThrough: null,
​​	modifiedAt: null,
​​	passThrough: null,
​​	projectId: "35957e27-fdcd-482f-8462-2cbe9ab6ca77",
​​	senderIdentifier: "[email protected]",
​​	senderIdentifierType: "email",
​​	source: "email",
​​	status: "queued",
​​	templateVariables: {
​​​	    companyName: "AcmeTasks",
​​​		groupMemberCount: "1",
​	​​	groupName: "xxcbn",
​​​		inviterName: "yngwie User"
	},
​​	views: 0,
​​	widgetConfigurationId: "baf28550-8dcc-451e-9ec2-d17888a79fb6"
}, ...]

onEvent

When passing an onEvent function, you are able to capture events generated by your users via the invitation form. For example, you might want to know when the user clicks on the “Invite” button in the invitation form you have during your onboarding process so that you can advance the user to the next step in the onboarding flow. See the Vortex Events section for a complete list of events.
// see Vortex Events section for event type definitions
const AppEvent = widgetRender | widgetSubmit | widgetSubmitEmailFocus | widgetSubmitEmailBlur | widgetEmailValidation | widgetShareLinkClick | widgetSubmitValidationError | widgetSubmitError | widgetError;

const onEvent = (event: AppEvent) => {
	console.log("Event:", event);
	processEvent(event);
};

<VortexInvite
          componentId={widgetID}
          isLoading={jwtLoading}
          jwt={jwt}
          onEvent={onEvent}
/>

isLoading

This prop is used to indicate that the invitation widget should not attempt to render yet, and instead render a loading placeholder. This can be used when all of the other props aren’t quite ready. For example:
export const vortexHarness = ({ parentIsLoading: boolean }) => {
const (jwt, jwtIsLoading) = useJwtHook();
const (isLoading, setIsLoading) = useState(parentIsLoading || jwtIsLoading);

useEffect(() => {
	setIsLoading(parentIsLoading || jwtIsLoading);
}, [parentIsLoading, jwtIsLoading]);

return (
<VortexInvite
          componentId={widgetID}
          isLoading={isLoading}
          jwt={jwt}
          onSubmit={onSubmit}
/>
);
}

unfurlConfig

Pass in the following information to be used when a shareable link is unfurled.
  • title - Title to be used in the unfurl. For example, “Invitation”.
  • description - Copy to be used in the unfurl. For example, “Join me on AcmeTasks!”
  • siteName - The name of your site or application. For example, “AcmeTasks”.
  • image - Image to use in the unfurl.
  • type - The object type that your unfurl represents. For most services this is likely to be website, but refer to the Open Graph protocal for the list of valid types.
unfurlConfig={{ title:"Invitation", description: "Join me on AcmeTasks to manage our projects", image: "https://acme-tasks.com/static/unfurl.png", siteName: "AcmeTasks", type: "website" }}