Realtime
One prop turns polling hooks into live ones: events push over a WebSocket the moment they happen, hooks refetch instantly, and polling drops to a once-a-minute safety net.
Enable it
<HandsetProvider baseUrl="/api/handset" realtime>
<App />
</HandsetProvider>That's the whole integration. The provider mints a short-lived token through your server proxy (POST /realtime/tokens — allowlisted in current next-routes / express-routes / remix-routes; one line to add if you installed the proxy earlier) and connects the browser directly to wss://media.handset.dev/v1/events. Your API key never reaches the client; the token expires in an hour and the connection re-mints itself.
What changes
Every polling hook — useConversations, useThread, useCalls, useCallTranscript, useVoicemails, usePortIn, useContactTimeline — refetches the instant a relevant event arrives (an inbound text renders in under a second instead of on the next 10s tick) and relaxes its interval to 60s as a safety net. Components need no changes; they inherit it from the provider.
Degradation is silent by design: if the proxy doesn't allowlist the mint, the socket can't connect, or the tab is offline, hooks keep polling at their normal cadence. Events are a latency optimization — webhooks to your backend remain the durable channel.
Raw events
For custom surfaces, subscribe to the stream directly — every envelope your webhooks would receive:
import { useHandsetEvents } from "@handset/react";
useHandsetEvents((event) => {
if (event.type === "call.transcript") {
console.log(event.data); // { call_id, speaker, text, … }
}
});