--- title: Employer Benefits Widget | Vitable Docs description: Embed Vitable's benefits administration dashboard into your application so employers can manage plans, enrollments, and coverage. --- The Employer Benefits widget lets you embed Vitable’s benefits administration experience directly into your application. Employers can view employee enrollments, manage plan assignments, and monitor benefits coverage — all without leaving your platform. ![Employer benefits administration dashboard](/images/drops/employer-benefits.jpeg) The widget renders inside an iframe and communicates with your app through a secure postMessage bridge. The `@vitable-inc/drops` SDK handles token management, message validation, and lifecycle events automatically. ## Prerequisites - A Vitable API key ([see Authentication](/getting-started/authentication/index.md)) - An employer onboarded with employees and eligibility configured — see [Employer Onboarding](/embedded_benefits/guides/employer-onboarding/index.md) - React 18+ for the frontend SDK Install the SDK: Terminal window ``` npm install @vitable-inc/drops ``` ## Set Up ``` import { VitableConnectProvider, EmployerBenefitsWidget } from "@vitable-inc/drops/react" import type { AccessTokenResponse } from "@vitable-inc/drops/react" const VITABLE_WIDGET_URL = "https://app.vitablehealth.com" function fetchToken(): Promise { return fetch("https://my.backend.com/api/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ employer_id: "empr_abc123" }), }) .then((res) => { if (!res.ok) throw new Error(`Token fetch failed: ${res.status}`) return res.json() }) .then((data) => ({ token: data.token, expiresIn: data.expiresIn })) } function App() { return ( console.log("Widget ready")} /> ) } ``` Wrap your app with `VitableConnectProvider`, pass a `fetchToken` function that hits your backend, and drop in the `EmployerBenefitsWidget`. The SDK handles token refresh, secure iframe communication, and session lifecycle — you just wire up the data source. ### Provider Props **`baseUrl`** `string` *required* The Vitable widget server URL. **`fetchToken`** `() => Promise<{ token: string; expiresIn: number }>` *required* Callback that returns a bound session token from your backend. Called on mount and automatically on refresh. **`allowedOrigins`** `string[]` Origins allowed for postMessage communication. Defaults to the origin derived from `baseUrl`. **`onError`** `(code: string, message: string) => void` Global error handler for all child widgets. **`contextKey`** `string` Change this value to force a fresh token fetch (e.g., when switching employers). **`theme`** [`VitableTheme`](/drops/overview#theming/index.md) Optional theme configuration to customize the widget’s appearance. See the [Theming](/drops/overview#theming/index.md) section for details. ## Backend: Token Endpoint The Employer Benefits widget requires a **bound access token** — a short-lived session token scoped to a specific employer. Bound tokens ensure the widget can only access data for the employer it was instantiated for, providing row-level security without exposing your API key to the browser. Your server needs an endpoint that calls the [Issue Access Token](/api/resources/auth/methods/issue_access_token/index.md) API to obtain a bound token for a given employer. The frontend will call this endpoint, so your API key stays server-side. The request uses `grant_type: "client_credentials"` with a `bound_entity` of type `"employer"`. The returned token is scoped exclusively to that employer. ``` import os import requests from flask import Flask, request, jsonify app = Flask(__name__) VITABLE_API_URL = os.environ["VITABLE_API_URL"] VITABLE_API_KEY = os.environ["VITABLE_API_KEY"] @app.post("/api/employer-token") def create_employer_token(): employer_id = request.json.get("employer_id") if not employer_id: return jsonify(error="employer_id is required"), 400 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": "employer", "id": employer_id}, }, ) if not response.ok: return jsonify(error="Token request failed"), response.status_code data = response.json() return jsonify(token=data["access_token"], expiresIn=data["expires_in"]) ``` Never expose your API key to the browser. The token endpoint must run server-side and should include appropriate authentication, CORS restrictions, rate limiting, and CSRF protection in production. ## Employer Benefits Widget Place the `EmployerBenefitsWidget` anywhere inside the provider. It renders an iframe containing the employer benefits administration dashboard. ``` import { EmployerBenefitsWidget } from "@vitable-inc/drops/react" function BenefitsAdmin() { return ( console.log("Widget ready")} onError={(code, message) => { console.error("Widget error:", code, message) }} /> ) } ``` ### Widget Props #### Styling **`width`** `string | number` — default `"100%"` Width of the iframe. **`height`** `string | number` — default `"600px"` Height of the iframe. **`className`** `string` CSS class applied to the iframe. **`style`** `CSSProperties` Inline styles applied to the iframe. #### Callbacks **`onReady`** `() => void` Fired when the widget iframe has loaded and is ready to interact. **`onEmployerBenefitsReady`** `() => void` Fired when the employer benefits view has fully initialized inside the iframe. Use this to know when the administration UI is visible and interactive. **`onTokenExpired`** `() => void` Fired when the session token has expired. The SDK handles refresh automatically — use this for logging or UI updates. **`onAuthError`** `(code: string, message: string) => void` Fired when authentication fails (e.g., invalid or revoked token). **`onError`** `(code: string, message: string) => void` Fired on any widget error. ## Token Lifecycle The SDK manages the full token lifecycle automatically: 1. **Initial fetch** — `fetchToken` is called when the provider mounts. 2. **Proactive refresh** — The token is refreshed automatically **2 minutes before expiration**. If the token lifetime is very short, the minimum refresh delay is 30 seconds. 3. **Retry with backoff** — If a fetch fails, the SDK retries up to 3 times with exponential backoff (1s, 2s, 4s delays). 4. **Widget initialization** — Once the iframe signals it’s ready, the SDK sends the token. The iframe acknowledges receipt before becoming interactive. 5. **Token updates** — When the token is refreshed, the SDK pushes the new token to the iframe automatically. 6. **Expiration** — If the token expires before a refresh succeeds, the iframe notifies the SDK, which triggers `onTokenExpired` and attempts a new fetch. Changing the `contextKey` prop on the provider forces a full reset — the current token is discarded and `fetchToken` is called again. Use this when switching between employers: ``` ```