Skip to main content
While the main integration point between your service and Vortex is the installation of the invitation widget in your application, that installation requires a JSON web token (JWT) which in turn requires an API key.

API Keys

API keys are scoped to an environment, so you’ll ultimately need to create one for each environment (i.e., development, staging and production). API keys have the following format: ${prefix}.${encodedId}.${key} The following snippet can be used to extract the real ID for the API key:
import { stringify as uuidStringify } from 'uuid';
const apiKey = process.env.VORTEX_API_KEY; // Your API key should NOT be committed with your source code and should be managed by a secret store.
const [prefix, encodedId, key] = apiKey.split('.'); // prefix is just VRTX
const id = uuidStringify(Buffer.from(encodedId, 'base64url'));
The ID and key are needed as part of signing the JWT, as outlined in the next section.
API keys are to be treated as sensitive data. Never expose your API key to customers. Signed JWTs are OK to expose to the user that is identified by it, but your API key must be guarded as a secret.

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 easiest way to generate a JWT is to leverage one of Vortex’s SDKs. If your language isn’t supported, 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 your user (i.e., the inviter), and includes the following required fields.
type payload {
	userIdentifier: string;
	groups: { groupType: string, groupName: string, groupId: string }[];
	Role: string;
	expires: number;
	identifiers: { type: string, value: string }[];
}
The information in the payload is used to ensure that your users are only taking valid actions based on their role and access when interacting with your invitation widgets. For example, a user of a B2B SaaS service shouldn’t be able to invite someone to a workspace that they themselves don’t have access to. The information you need to include in the payload depends on whether or not you are generating the JWT on-demand. This is particularly relevant to the group parameter identified below.Groups are the distinct types of entities to which someone can be invited in your service. Most services have just one grouping construct. For example, a B2B SaaS may have workspaces as the grouping that users get invited to. Some services do have more than one. For example, a project management service like Trello allows users to be invited to a workspace, or to a board within a workspace. If you are generating the JWT on demand, the groups you include in the payload only need to be those relevant to the invitation being generated. For example, if the user is in the context of a specific workspace and trying to invite people to that workspace, then the current workspace is the only relevant group.On the other hand, if the user is trying to invite people to a project within that workspace and they can select the project as part of the invitation process (i.e., the project context isn’t already established), then you’ll want to include all projects to which the user has access in the list of groups.
const userId = ‘55bab438-0492-4dd6-868b-fc93b5b06e32’; // should be the id of the user in your service
const groups = [ // fetch this from your database in a real-world use case
	{
		type:workspace’, // a name for the different group types the Vortex customer’s system has
		name:Acme Co’, // the name of this particular group
		id: ‘5cff5e56-b15b-4104-813e-0d1597c26285// the id of the group in the Vortex customer’s system
	},
	{
		type:workspace’,
		name:Foobar Inc’,
		id:eec18848-14cf-4726-a0fc-91805361b781
	},
	{
		type:project’,
		name:Public Launch Project’,
		id: ‘4c14cfa2-3346-4672-b03c-76ba597b4166
	},
];
const role =admin’;
const expires = Math.floor(Date.now() / 1000) + 3600;
const identifiers = [
	{
		type:email’,
		value:foo@bar.com
	},
];
const header = {
    iat: Date.now(),
    alg: 'HS256',
    typ: 'JWT',
    kid: id
};
const payload = {
    userId,
    groups,
    role,
    expires,
    identifiers,
};
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.

API Calls

Allowing your invitees to accept invitations to your service means integrating a couple of endpoints into your registration flow. In addition to creating an invitation acceptance flow, additional use cases for leveraging the invitation information on Vortex are outlined below. Refer to our API documentation for details on the referenced endpoints.

Accepting an invitation

This is the flow for when someone clicks on an invitation link they received via email, or that was shared with them via some other means.
1

Parse the invitation ID from URL

When an invitee clicks on an invitation link they are redirected to the landing page specified in your invitation flow’s configuration. The ID of the invitation will be passed in on the query string. For example:https://acme-tasks.com/invites/accept?invitationId=3457f7fd-a312-40a5-8284-4c2f87c8aed1Note that you’ll want to persist the invitation ID through your registration process, as you’ll need it later to mark the invitation as accepted.
2

Get the invitation details

  • Send a GET request to /invitations/{invitationId}.
    • This will return information about the invitation, including the inviter’s ID and email address, the invitee’s email address (unless it’s a shareable link), the group the invitation is associated with (ID, type, name) and any custom field values collected during the invitation process (e.g., Role).
    • If the endpoint returns a 404 the invitation is no longer valid and you should provide appropriate messaging to the invitee.
  • Validate the group (optional)
    • For most services an invitation will be associated with a specific group (e.g., workspace, organization, team, document, etc.). You should confirm that the group the invitation is associated with is still valid. For example, if someone was invited to join a specific workspace, you’ll want to ensure that workspace still exists before allowing the invitation to be accepted. If your service involves more than one type of group be sure to refer to the group’s type so that you know what type of entity you’re validating. Assuming the group is still valid, you can allow the user to proceed with accepting the invitation.
3

Check for additional invitations (optional)

  • Prior to joining your service, someone may receive invitations to more than one group. For example, bob@acme.com may have had three different people invite him to three different projects on your task-management service. Many services only allow someone to accept the specific invitation associated with the link they clicked on, while others allow the user to choose from any additional open invitations for the given email address. Some services may even automatically accept the additional invitations.
  • Send a GET request to /invitations with targetType being “email” and targetValue being the email address returned by /invitations/.
4

Mark the invitation(s) as accepted

Once the user completes the signup process, send a POST request to /invitations/accept with the IDs of the invitations being accepted. The email address with which the user registered must be included.

Registration, independent of an invitation

When someone signs up for your service directly, without coming in via an invitation link, it’s still important to check for open invitations to make sure that the user ends up in an existing workspace/org/team/etc. instead of unnecessarily creating a new one. This is a very common problem with B2B SaaS services, especially ones where content can be shared with unregistered people. Let’s use Slack as an example. Say bob@acme.com has been invited to three different Slack workspaces. For whatever reason, Bob doesn’t use any of the invitations he received, and instead goes directly to slack.com to sign up. As soon as his email address has been confirmed, Slack presents Bob with the three workspaces that he can join, as well as an option to create a new workspace. This flow would look something like this:
1

User signs up for your service

2

Check for open invitations

Send a GET request to /invitations with targetType being “email” and targetValue being the email address used at registration. This will return a list of open invitations to the given email address.
3

Present invitations to the user (optional)

If any additional invitations were returned, it’s up to you to decide what to do with them based on the nature of your service. In the previous Slack example, the user can select just one workspace to join at a time, which makes sense because each workspace on Slack requires its own set of credentials. For some services, allowing the user to select multiple invitations to accept at once may make sense. And for others, you might want to simply have all open invitations accepted automatically upon registration. For example, if you’re invited to join multiple boards on Trello, when you sign up you’re automatically a member of all of those boards.
4

Mark the invitation(s) as accepted

If any invitations were presented and accepted, once the user completes the signup process, send a POST request /invitations/accept with the IDs of the invitations being accepted.

In-product invitation acceptance

Invitations to join your service are obviously not the extent of the invitations that may be relevant to you. Even once a user has signed up, depending on the nature of your service, they may get invited to join boards or documents, or to connect with other users. Or there may still be additional open invitations from before they signed up. Either way, if you want to have an in-product invitation acceptance experience…
1

Check for open invitations

Send a GET request to /invitations with targetType being “email” and targetValue being the email address of the given user.
2

Present invitations to the user

Provide a UX for the user to select one or more invitations to accept.
3

Mark the invitation(s) as accepted

Send a POST request to /invitations/accept with the IDs of the selected invitations.

Displaying a list of open invitations

You might want to display a list of open invitations on the Members page of a workspace, or in an admin area, of a B2B service, or on a user’s Connections page in a community site. To get a list of all open invitations, send a GET request to /invitations/by-group/{groupType}/{groupId} where groupType is the type of grouping (e.g., workspace, org, etc.) and groupID is its internal ID.

Revoking an invitation

As part of displaying a list of open invitations, as described above, you may want to include a link or button to revoke/delete an invitation. Often this capability is limited to an admin, or possibly the user that generated the invitation. That link should send a DELETE request to the /invitations/{invitationId} endpoint to delete the given invitation.

Resending an invitation

Also as part of displaying a list of open invitations you may want to include a link or button to resend the invitation. Send a POST request to /invitations/{invitationId}/reinvite using the invitationId associated with the invitation being resent.