Set Up Vitable Drops

Install the SDK, issue secure bound tokens, and configure the shared React provider.

Every Drop uses the same three-part setup: install the SDK, expose an authenticated backend endpoint that issues bound tokens, and configure VitableConnectProvider in your React application.

Before You Begin

  • A Vitable API key stored on your backend. See Authentication.
  • A Vitable employee or employer ID for the signed-in user.
  • React 18 or later.

1. Install the SDK

npm install @vitable-inc/drops

2. Create a Server-Side Token Endpoint

Your backend must authenticate the caller, confirm that they may access the requested employee or employer, and exchange its API key for a bound access token.

import os
import requests
from flask import Flask, jsonify, request
app = Flask(__name__)
VITABLE_API_URL = os.environ["VITABLE_API_URL"]
VITABLE_API_KEY = os.environ["VITABLE_API_KEY"]
@app.post("/api/vitable/token")
def create_vitable_token():
payload = request.get_json(silent=True) or {}
entity_type = payload.get("entity_type")
entity_id = payload.get("entity_id")
if entity_type not in {"employee", "employer"} or not entity_id:
return jsonify(error="A valid entity_type and entity_id are required"), 400
# Authenticate the caller and authorize access to this entity here.
response = requests.post(
f"{VITABLE_API_URL}/v1/auth/access-tokens",
headers={
"Authorization": f"Bearer {VITABLE_API_KEY}",
"Content-Type": "application/json",
},
json={
"grant_type": "client_credentials",
"bound_entity": {"type": entity_type, "id": entity_id},
},
timeout=10,
)
response.raise_for_status()
token = response.json()
return jsonify(
token=token["access_token"],
expiresIn=token["expires_in"],
)

The authorization check in this example is intentionally application-specific. Do not issue a token based only on an ID supplied by the browser. Confirm that the signed-in user may access that employee or employer, and apply appropriate CORS, CSRF, and rate-limit controls.

3. Configure the Provider

The provider calls your backend on mount and again when the token needs to be refreshed:

import { VitableConnectProvider } from "@vitable-inc/drops/react"
import type { AccessTokenResponse } from "@vitable-inc/drops/react"
import { useCallback } from "react"
import type { PropsWithChildren } from "react"
const VITABLE_WIDGET_URL = "https://app.vitablehealth.com"
function createTokenFetcher(
entityType: "employee" | "employer",
entityId: string,
): () => Promise<AccessTokenResponse> {
return async () => {
const response = await fetch("/api/vitable/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
entity_type: entityType,
entity_id: entityId,
}),
})
if (!response.ok) {
throw new Error(`Token request failed: ${response.status}`)
}
return response.json()
}
}
type EmployeeDropSessionProps = PropsWithChildren<{ employeeId: string }>
function EmployeeDropSession({ employeeId, children }: EmployeeDropSessionProps) {
const fetchToken = useCallback(
createTokenFetcher("employee", employeeId),
[employeeId],
)
return (
<VitableConnectProvider
baseUrl={VITABLE_WIDGET_URL}
fetchToken={fetchToken}
contextKey={employeeId}
>
{children}
</VitableConnectProvider>
)
}

Use employee for the Employee Dashboard and employer for both employer widgets.

Provider Props

PropTypeRequiredPurpose
baseUrlstringYesVitable widget server URL
fetchToken() => Promise<{ token: string; expiresIn: number }>YesFetches a bound session token from your backend
contextKeystringNoResets the session when the active employee or employer changes
allowedOriginsstring[]NoRestricts postMessage origins; defaults to the baseUrl origin
onError(code: string, message: string) => voidNoHandles errors from child widgets
themeVitableThemeNoCustomizes the embedded UI; see Theming

Token Lifecycle

The SDK fetches a token when the provider mounts, refreshes it before expiration, retries transient fetch failures with backoff, and sends updates to the iframe. Changing contextKey discards the existing session and requests a token for the new context.

Use a stable contextKey that matches the bound entity ID. Never reuse an employee-bound token for another employee or an employer-bound token for another employer.

4. Add a Widget

Continue with the component you need:

Production Checklist

  • Authenticate every token request and authorize the requested entity.
  • Keep the Vitable API key in server-side secret storage.
  • Allow postMessage communication only with the expected Vitable origin.
  • Reset contextKey whenever the active employee or employer changes.
  • Record token-fetch and widget errors without logging tokens.

Theming

Every Drop supports theming through the VitableConnectProvider. Pass a theme prop to customize colors, fonts, logo, and color mode so the embedded experience matches your application’s look and feel.

import type { VitableTheme } from "@vitable-inc/drops/core"
const theme: VitableTheme = {
fontFamily: '"Inter", sans-serif',
primaryColor: "brand",
colorMode: "light",
colors: {
brand: [
"#eff6ff", "#dbeafe", "#bfdbfe", "#93c5fd", "#60a5fa",
"#3b82f6", "#2563eb", "#1d4ed8", "#1e40af", "#1e3a8a"
],
gray: [
"#f8fafc", "#f1f5f9", "#e2e8f0", "#cbd5e1", "#94a3b8",
"#64748b", "#475569", "#334155", "#1e293b", "#0f172a"
],
},
logoUrl: "https://your-cdn.com/logo.png",
}

Theme Properties

PropertyTypeDescription
fontFamilystringCSS font family for all text
primaryColorstringKey into the colors object that identifies the primary palette
colorMode"light" | "dark" | "system"Color mode for the embedded UI
colorsRecord<string, string[]>Named color palettes — each is a 10-element array from lightest to darkest (hex format)
fontSizes{ xs, sm, md, lg, xl }CSS lengths for the type scale
lineHeights{ xs, sm, md, lg, xl }CSS lengths for line heights
logoUrlstringHTTPS URL to your logo image

The primaryColor value must be a key that exists in colors. For example, if primaryColor is "brand", there must be a colors.brand array. The logoUrl must use HTTPS.

Theme updates are applied in real time — if your app supports theme switching, the embedded widgets will update automatically when you change the theme prop.