HIPAA-eligible video visits
Run secure, in-browser video appointments on Twilio Programmable Video. Drop in the prebuilt <VideoRoom />, or call the token endpoint and bring your own UI. The browser only ever receives a short-lived Twilio access token — never a Twilio API key.
How it works
Every video booking maps to a Twilio room. When the provider starts the call, the backend mints short-lived Twilio access tokens for each participant. The patient gets theirs from a keyless endpoint — via an emailed /j/{token} link, the keyless manage page, or the patient portal. You pass that token (with the room name) to the twilio-video SDK's connect(token, { name: room_name }).
Tokens, not secrets
Option 1 — Prebuilt <VideoRoom />
The fastest path: <VideoRoom /> from @cadence/react connects, renders the remote participant(s) plus your own camera as a self-view, and gives you mute / camera / leave controls — all themable with a single accent prop.
@cadence/react is in private beta and not yet on npm. The snippets below are the usage you'll reach for once it's published. Get early access.Install
npm install @cadence/react twilio-video
# twilio-video is browser-only; @cadence/react loads it dynamically.VideoRoom is client-only — it loads twilio-video with a dynamic import(), so it never runs during SSR/build. Render it inside a "use client" boundary:
"use client";
import { useEffect, useState } from "react";
import { CadenceClient, VideoRoom, type VideoJoin } from "@cadence/react";
export function JoinCall({ joinToken }: { joinToken: string }) {
const [join, setJoin] = useState<VideoJoin | null>(null);
useEffect(() => {
// The keyless video endpoints carry org + identity in the token,
// so `org` is irrelevant here.
const client = new CadenceClient({ org: "" });
void client.getVideoJoin(joinToken).then(setJoin);
}, [joinToken]);
if (!join?.live || !join.token || !join.room_name) {
return <p>Waiting for your provider to start the call…</p>;
}
return (
<VideoRoom
token={join.token}
roomName={join.room_name}
accent="#1f5e45"
onLeave={() => setJoin(null)}
/>
);
}The keyless endpoints return { live: false, token: null } until the provider starts the call. The useCadenceVideo hook polls for you and flips to ready automatically:
import { CadenceClient, VideoRoom, useCadenceVideo } from "@cadence/react";
import { useCallback, useMemo } from "react";
const client = useMemo(() => new CadenceClient({ org: "" }), []);
const { status, token, roomName } = useCadenceVideo(
useCallback((s) => client.getVideoJoin(joinToken, s), [client, joinToken]),
);
// status flips "waiting" → "ready" the moment the provider starts the call.
if (status === "ready") return <VideoRoom token={token!} roomName={roomName!} />;Option 2 — Fully custom UI
Prefer to build your own call screen? You don't need <VideoRoom /> at all. Call the token endpoint yourself and hand the token to your own twilio-video integration:
import { connect } from "twilio-video";
// 1. Mint a short-lived token from any Cadence video endpoint.
const res = await fetch("https://cadence.abhix-ai.com/api/v1/public/video/token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ token: joinToken }),
});
const { live, token, room_name } = await res.json();
// 2. If the call is live, hand the token straight to twilio-video.
if (live && token && room_name) {
const room = await connect(token, { name: room_name, audio: true, video: true });
// …attach tracks and render your own grid.
}The token is a Twilio Video AccessToken JWT — pass it straight to connect(token, { name: room_name }) and render participants however you like.
Starting & ending a call (host)
Providers start, refresh, and end the call from authed endpoints (an org API key, or a dashboard/provider session via Authorization: Bearer + X-Cadence-Org):
# Provider/host (authed: org API key, or a dashboard/provider session).
POST /api/v1/bookings/{id}/video/start -> { room_name, token, identity, join_url, start_at }
POST /api/v1/bookings/{id}/video/token -> { room_name, token, identity }
GET /api/v1/bookings/{id}/video -> { room_name, started_at, ended_at, live }
POST /api/v1/bookings/{id}/video/end -> statusstart creates the room and returns a host token plus a join_url you can share. Once started, the patient's keyless endpoints begin returning live tokens.
The patient portal
Patients don't need an account or a per-booking link to manage care: the passwordless patient portal at /{org}/portal signs them in with an emailed magic link, then lists their upcoming and past appointments. Video visits show a prominent Join video call button the moment the provider starts the call, and patients can reschedule, cancel, or book a new appointment — all in their org's timezone.
Link to it
https://cadence.abhix-ai.com/your-org/portalThe portal session is a patient-scoped token (it carries the org and the customer), held in the browser — it is never an org secret. Authed portal calls need only an Authorization: Bearer header.
HIPAA & your Twilio account
You bring your own Twilio BAA