Self-Hosted
Self-Hosted mode lets you bring your own agent infrastructure. Instead of Dial’s managed agent driving a call, Dial connects to a WebSocket server you host. It comes in two variants — one is active at a time, for all of your account’s calls:
- LLM (
"type": "llm") — Dial still handles the hard parts of voice: telephony, speech‑to‑text, text‑to‑speech, turn‑taking, and interruptions. Your server only answers one question, live: given the conversation so far, what should the agent say next? It speaks the Self-hosted LLM protocol — transcripts in, text turns out. - Audio (
"type": "audio") — Dial pipes the raw call audio to your server, full duplex, over the Self-hosted audio protocol. You bring the entire voice stack: a speech-to-speech model (OpenAI Realtime, Gemini Live, …) or your own STT → LLM → TTS chain. Audio formats are configurable per direction (G.711 or linear PCM at 8–24 kHz).
Either way, no voice-infrastructure details leak through — your server sees clean Dial-native protocols and Dial call ids only.
How it works
- You host a WebSocket endpoint and configure Self-Hosted with its
wss://URL and variant. - For each call, Dial opens a socket to
wss://<your-url>/<call_id>, signed so you can verify it’s from Dial. - LLM: Dial streams the live transcript and asks for the agent’s turns; your server streams back the words to speak. Audio: Dial streams the caller’s audio; your server streams back the agent’s audio.
While Self-Hosted is on, it drives all of your account’s calls — inbound and outbound.
Request access
Self-Hosted hands the conversation to a server we don’t run, so it’s approved per account before you can point calls at one.
Send one request_access action describing what you’re building. A human reads it, so the more
concrete you are about who you’ll be calling and why your own server needs to drive the
conversation, the faster it clears.
GET /api/v1/self-hosted reports where you stand in access:
Until access is granted, save and activate return 403. disable and request_access
always work, so an account can always switch Self-Hosted off.
Enable it
Once access is granted, set it up from the dashboard Self-Hosted page, or via the API. LLM and audio each have their own independent config — configure either without affecting the other.
Every change is a single POST with one action: save (persist a mode’s config; mints that
mode’s signing secret on the first URL), activate (save the config, then enable + route calls
through that mode — enable or switch), or disable.
save never changes which mode is active or whether Self-Hosted is on — it just persists that
mode’s config. Each mode has its own wsUrl and signing secret.
Copy that mode’s signing secret — from the dashboard, or
GET /api/v1/self-hosted/secret?mode=llm — and wire up your server’s signature verification with it
(each mode has its own secret). Then activate: the body is exactly a save (the mode’s
config), and it also enables Self-Hosted and routes all your calls (inbound and outbound) at
that mode’s server immediately — so make sure it’s ready:
To switch modes, activate the other one — its config comes along in the same body (affects new
calls only). To turn Self-Hosted off, disable:
Pipecat Cloud
If your voice agent runs on Pipecat Cloud,
point the audio mode at the agent instead of a URL, and leave Pipecat’s WebSocket token auth on
(websocket_auth = "token"). Before every connection Dial calls the agent’s
/start endpoint with your
Pipecat Cloud public key and connects with the single-use token it returns. That includes reconnecting
mid-call, so a token is never reused.
pipecatCloud takes the place of wsUrl: send one or the other, and saving one clears the other.
Treat the Pipecat Cloud public key as a secret. Anyone who has it can start sessions on your agent, which Pipecat bills to you. Never share it, commit it, or put it in client-side code. Dial stores it encrypted and only ever shows it masked.
Your bot receives the call on the WebSocket and speaks the
audio protocol. Start from the
Pipecat Cloud echo playbook:
a ready-to-deploy bot with Pipecat Cloud’s bot(runner_args) entrypoint and DialFrameSerializer,
which plays the caller’s voice back so you can prove the whole path before adding any AI. Then swap
in your own pipeline; the Pipecat playbook
has a full speech-to-text, LLM, and text-to-speech example. The /start body, runner_args.body in
your bot, carries call_id, direction, from, and to.
Set min_agents to at least 1 in pcc-deploy.toml for real traffic. With no warm agent, Pipecat
accepts the connection but no bot is running to answer it. Dial notices the silence and reconnects
to a warm agent, but the caller hears several seconds of nothing first.
Pipecat authenticates the connection with its token, so your bot doesn’t need to verify
X-Dial-Signature.
When a call fails
If Dial can’t connect a call to your target, the call ends as failed, and its
failureReason says why. The dashboard shows the same reason under
the call’s status.
The same value is on the call.ended and
call.status_changed events.
Cancelling a call before it connects
On an outbound call, Dial opens the socket to your server only once the callee answers. Until
then the call is Queued or Ringing, and you can cancel it with
Stop a call (POST /api/v1/calls/{id}/stop, authenticated with
your API key, like every other call endpoint). Your server is never contacted, nothing is
charged, and the call ends with terminationType: "canceled".
The request returns before the call is over. Wait for call.ended
(it carries canceled: true) to know it has ended. If the callee answers in the instant before the
stop lands, the call is hung up instead: it ends as completed and is billed for the seconds it
was connected. The same endpoint hangs up a call that’s already in progress — or send
end_call from your socket.
Verify every connection
Each connection carries an X-Dial-Signature header (HMAC‑SHA256 over <timestamp>.<call_id>
with your signing secret) — the same scheme in both variants. Your server should verify it and
reject stale or mismatched signatures. The SDKs ship a helper for
exactly this, plus the full protocols as ready-made schemas and server helpers — start there rather
than hand‑rolling the message types.
Reference servers
Don’t start from scratch. The
OpenAI WebSocket server
in the Dial playbooks repo is a minimal, runnable LLM-variant server. It verifies the
X-Dial-Signature, drives each turn with the OpenAI SDK, echoes the ping_pong keepalive, and
focuses on transcript interrupts — cancelling the in‑flight reply when the caller speaks again so
the agent never talks over them. Run it, expose it with ngrok, and point
Self-Hosted at the tunnel’s wss:// URL:
While Self-Hosted is on, managed features do not apply: built‑in agent tools and any Context MCP connections are inert, because the agent driving the call is yours. In the audio variant this extends to everything voice: transcripts, recordings, and call summaries are not produced — Dial never hears the conversation. Turn Self-Hosted off to return to the managed agent.
Next steps
- Self-hosted LLM protocol — the LLM-variant WebSocket contract: connection, signing, messages, streaming, interrupts, and resumability.
- Self-hosted audio protocol — the audio-variant contract: formats, media frames, barge-in, checkpoints, and reconnection.
- SDKs — protocol schemas, server helpers, and the signature helper for Node and Python.