Connect by JBRH Open Connect

Connect's own voice

On the carrier path the words a caller hears are synthesised by Connect, not by the carrier. A sentence goes to Google's Text-to-Speech API, comes back as a small 8 kHz MP3, is stored by content digest and handed to the carrier as a <Play>. If any part of that fails, the carrier's own voice reads the line instead, so a caller is never met with silence.

Status
Available What this means
Audience
both
Channels
phone
In the app
#/phone
Last verified
Product version
6.3.2

What the carrier's voice could not do#

Until 2026-09-04 every word a caller heard was the carrier's own text-to-speech: two voices, sixteen European locales, and no Indian language at all. For a business whose customers speak Telugu, Hindi or Tamil that is not a quality problem, it is a wall. The line could answer in English or it could not answer.

voice_tts.py replaces that stage. The sentence Connect wants said goes to Google's Text-to-Speech API and comes back as audio, and the carrier is asked to play a file rather than read a string. The rest of the call — the webhook shape, the turn handler, the <Gather> — is unchanged, because the carrier adapter is the only layer that knows the difference between playing and speaking.

FamilyVoicesHow you steer it
gemini-ttsOne voice that covers every languageA natural-language style instruction — you describe the delivery in words
chirp3-hdOne voice per localeA speaking rate — a number, not a description

From a sentence to a URL#

  1. Connect decides on the exact words — a greeting, a reply, the closed-line message.

    Result The text is deterministic at this point, which is what makes the next step cacheable.

  2. The text, the model, the voice, the style and the language are hashed into one key and the store is checked.

    Result A sentence this line has said before is already audio; nothing is synthesised and nothing is charged for it again.

  3. On a miss, the API is asked for a small 8 kHz MP3 — telephone bandwidth, not studio bandwidth, because the PSTN leg will discard the difference anyway.

    Result The file is stored content-addressed: the digest of the audio is its name, so identical audio is stored once.

  4. The carrier is handed <Play> pointing at /api/voice/audio/<digest>.mp3.

    Result That route is served without a session — the carrier is not a signed-in browser and cannot be made into one. The digest in the path is the only thing that identifies the file, and it carries no workspace, no caller and no readable text.

Never silence#

The failure mode this design is most afraid of is not a bad voice, it is no voice. A caller on a live line who hears nothing has no way to tell a broken system from a rude one, and the call record afterwards reads as though they hung up.

  • speak_url returns an empty string on any failure — a refused key, a quota answer, a timeout, a malformed response. An empty string is not an error the caller experiences; the carrier's own voice reads the line, in whichever of its sixteen locales fits.
  • A refusal that looks like a credential problem puts the whole synthesiser aside for ten minutes (COOLOFF_SECONDS), so a broken key does not add a failed round trip to every sentence of every call for the rest of the hour.
  • Saving the setting lifts the cool-off immediately. That is deliberate: the person fixing the key should not have to wait out a timer to find out whether they fixed it.

Where this applies, and where it does not#

This is the carrier turn-based path: a webhook hears a sentence, Connect writes a reply, the carrier plays it. On the realtime engine the model itself speaks — it is speech to speech, and there is no synthesiser in the reply path at all.

Connect's own text-to-speech still does two jobs on the realtime path. The greeting is synthesised ahead of the call and played the instant the caller picks up, because the live model refuses to say a line it did not generate. The closed-line refusal is synthesised the same way and played on a track of its own before the call ends.

The settings live on the Phone screen's Behaviour section, for both audiences over the same implementation. A customer workspace picks its synthesiser family, its voice and its style exactly where the operator does.

Questions#

Can somebody guess an audio URL and hear a customer's call?

The route serves one synthesised sentence, named by the digest of its own audio. It holds no conversation, no caller and no workspace, and there is no way to enumerate digests. What it does contain is the business's own outgoing wording — a greeting or a closed-line message — which is spoken to anyone who rings the number anyway.

Does every sentence of a live conversation get synthesised?

On the carrier path, yes — each reply is a fresh sentence and a fresh request, cached only when the exact wording repeats. Greetings, closed-line messages and other fixed lines repeat constantly and are effectively always cached; a genuine reply to a genuine question almost never is.

Which family should a multilingual line use?

gemini-tts has one voice for every language, so a line that switches between languages keeps the same voice. chirp3-hd has a voice per locale and a speaking rate, which is steadier if the line only ever speaks one language and you care about pace.