A BYO media session
What happens on the wire between Tone and your own voice stack, from dial to hang-up.
See Bring your own voice stack for setup and Tone Media Streams for the protocol reference.
The setup, once
| # | Step | Where |
|---|---|---|
| 1 | Declare your sender classification | Dashboard |
| 2 | Store the endpoint credential, then Copy ID | Dashboard — Settings → Secrets |
| 3 | PATCH /v1/numbers/{id} with routingMode: "byo_ws" and mediaEndpoint | API |
Steps 1 and 2 are dashboard-only and will 401 with an API key, deliberately.
In step 3, routingMode: "byo_ws" must come with mediaEndpoint in the same
request unless the number already has one stored — otherwise 422. Clearing
the endpoint of a byo_ws number is also 422. byo_sip cannot be set or
cleared by PATCH (409); use POST/DELETE /v1/numbers/{id}/sip.
Then, per call
| # | What happens |
|---|---|
| 1 | POST /v1/calls without agentId — sending one is a 422 |
| 2 | The compliance gate runs, exactly as it does for an agent call |
| 3 | The carrier dials |
| 4 | Tone opens a WebSocket to your mediaEndpoint |
| 5 | A start frame arrives with the call id, direction, from, to and your customParameters |
| 6 | media frames flow both ways until someone hangs up |
| 7 | Tone writes the call record and bills at the BYO rate |
Inbound needs no extra work: a BYO number answers inbound the same way.
With a tone_test_ key and a sandbox number, steps 3–6 never happen: the
sandbox carrier closes the call on a timer and Tone opens no WebSocket to your
endpoint. The dial, the compliance gate, the call record and the webhooks are
real. Exercising your endpoint takes a live number and a real, billed call.
Tone forwards DTMF to your bot but records no opt-out on a BYO call, and runs no
extraction (summary and outputs stay null). Record opt-outs with
POST /v1/compliance/call-outcomes or POST /v1/consent/opt-out.
The endpoint
static names one wss:// URL, used for every call with the call id
appended as a query parameter. webhook names an https:// URL fetched per
call that answers {"url":"wss://…"} — which is how you route calls to different
backends.
🔴 Both must be publicly reachable. Private and link-local addresses are refused when you save the config, and refused again at connect time after DNS resolution — because the first check can be sidestepped by DNS and the second by a config that was never validated.
Three rules that decide whether it sounds right
- Send small chunks, 20–100ms. One large buffer is a caller waiting.
- Use
markto know when your audio finished playing. Guessing from byte counts drifts. Marks are echoed on Exotel and Plivo calls; on a call through Tone's SIP gateway they are currently not echoed. - Hang up by closing the socket. There is no separate hang-up frame.
customParameters merge in three layers, later winning: static number config →
per-call variables → identity facts (callId, direction, from, to).
Every refusal reports a completed call
If Tone cannot reach you, the call still ends properly — it is never left hanging in your CDR:
endReason | |
|---|---|
refused_no_endpoint | The number is byo_ws with no mediaEndpoint, or its authSecretId can no longer be decrypted |
refused_endpoint_unreachable | Your endpoint did not accept the connection — a 401/403 or any failed upgrade — or a webhook endpoint answered non-2xx |
refused_endpoint_forbidden | It resolved into private or forbidden address space |
refused_media_format | The negotiated audio format is not one we speak |
refused_capacity | Tone's BYO capacity was full |
refused_internal_error | Something failed on Tone's side |
caller_hung_up_during_resolve | The caller hung up before your endpoint was opened |
error | The connection to your endpoint errored during the call; always failed |
max_duration | The 60-minute ceiling. Ends answered, and is billed |
Every refused_* writes a call record with a failed disposition, bills
nothing, and fires call.completed and call.failed. The dashboard's call
detail shows the reason as "Ended because …". Watch for these before blaming the
carrier.
Two limitations
Campaigns do not run on BYO numbers — refused at launch rather than as thousands of per-call errors.
A negotiated full-stack rate does not apply. BYO bills the telephony-plus-platform subset; see Billing.