Employer Billing Widget
Embed Vitable's billing dashboard into your application so employers can view invoices, payments, and billing history.
The Employer Billing widget lets you embed Vitable’s billing management experience directly into your application. Employers can view invoices, track payments, and review billing history — all without leaving your platform.
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
Section titled “Prerequisites”- A Vitable API key (see Authentication)
- An employer onboarded with employees and eligibility configured — see Employer Onboarding
- React 18+ for the frontend SDK
Install the SDK:
npm install @vitable-inc/dropsSet Up
Section titled “Set Up”import { VitableConnectProvider, EmployerBillingWidget } from "@vitable-inc/drops/react"import type { AccessTokenResponse } from "@vitable-inc/drops/react"
const VITABLE_WIDGET_URL = "https://app.vitablehealth.com"
function fetchToken(): Promise<AccessTokenResponse> { 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 ( <VitableConnectProvider baseUrl={VITABLE_WIDGET_URL} fetchToken={fetchToken} contextKey="empr_abc123" > <EmployerBillingWidget height="800px" onReady={() => console.log("Widget ready")} /> </VitableConnectProvider> )}Wrap your app with VitableConnectProvider, pass a fetchToken function that hits your backend, and drop in the EmployerBillingWidget. The SDK handles token refresh, secure iframe communication, and session lifecycle — you just wire up the data source.
Provider Props
Section titled “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
Optional theme configuration to customize the widget’s appearance. See the Theming section for details.
Backend: Token Endpoint
Section titled “Backend: Token Endpoint”The Employer Billing widget requires the same employer-bound access token as the Employer Benefits Widget. If you already have an employer token endpoint, you can reuse it for billing.
The request uses grant_type: "client_credentials" with a bound_entity of type "employer". The returned token is scoped exclusively to that employer.
import osimport requestsfrom 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"])Employer Billing Widget
Section titled “Employer Billing Widget”Place the EmployerBillingWidget anywhere inside the provider. It renders an iframe containing the employer billing dashboard.
import { EmployerBillingWidget } from "@vitable-inc/drops/react"
function BillingDashboard() { return ( <EmployerBillingWidget height="800px" onReady={() => console.log("Widget ready")} onError={(code, message) => { console.error("Widget error:", code, message) }} /> )}Widget Props
Section titled “Widget Props”Styling
Section titled “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
Section titled “Callbacks”onReady () => void
Fired when the widget iframe has loaded and is ready to interact.
onEmployerBillingReady () => void
Fired when the employer billing view has fully initialized inside the iframe. Use this to know when the billing 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.
Combining Benefits and Billing
Section titled “Combining Benefits and Billing”You can embed both employer widgets under the same provider — they share the same employer-bound token:
import { VitableConnectProvider, EmployerBenefitsWidget, EmployerBillingWidget } from "@vitable-inc/drops/react"
function EmployerPortal() { return ( <VitableConnectProvider baseUrl={VITABLE_WIDGET_URL} fetchToken={fetchToken} contextKey={currentEmployerId} > <h2>Benefits</h2> <EmployerBenefitsWidget height="600px" />
<h2>Billing</h2> <EmployerBillingWidget height="600px" /> </VitableConnectProvider> )}Token Lifecycle
Section titled “Token Lifecycle”The SDK manages the full token lifecycle automatically:
- Initial fetch —
fetchTokenis called when the provider mounts. - 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.
- Retry with backoff — If a fetch fails, the SDK retries up to 3 times with exponential backoff (1s, 2s, 4s delays).
- Widget initialization — Once the iframe signals it’s ready, the SDK sends the token. The iframe acknowledges receipt before becoming interactive.
- Token updates — When the token is refreshed, the SDK pushes the new token to the iframe automatically.
- Expiration — If the token expires before a refresh succeeds, the iframe notifies the SDK, which triggers
onTokenExpiredand attempts a new fetch.