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, … }
  }
});