Connect two callers who speak different languages on the same phone call. Each caller speaks in their own language and hears the other in their own language — live, on the same call, no interpreter in the loop.
No Google Translate. No AWS Translate. No DeepL. No third-party translation API. Just Telnyx Voice Call Control (for the phone call) and Telnyx AI Inference (for the translation). Same API key, same platform, same billing.
141 lines of Python. Here's how it works.
What It Does
You POST two phone numbers and two languages:
curl -X POST http://localhost:5000/bridge \
-H "Content-Type: application/json" \
-d '{"number_a": "+13125550001", "lang_a": "English",
"number_b": "+5215550002", "lang_b": "Spanish"}'
The app calls caller A. When they answer, it calls caller B. When B answers, the translation bridge goes live:
- Caller A speaks in English
- Telnyx transcribes the speech → sends to your webhook
- Your app sends the transcript to AI Inference: "Translate from English to Spanish"
- The translated text is spoken to caller B via TTS in Spanish
- Caller B speaks in Spanish → transcribed → translated to English → spoken to caller A
- Loop until either caller hangs up
Both callers stay on the same phone call. Neither installs anything. Translation happens server-side, transparently.
The Architecture
Everything lives in one Flask file. No database, no Redis, no message queue. Bridge state lives in an in-memory dict, keyed by a bridge ID that travels in Telnyx's client_state field on every webhook event.
POST /bridge → call A → A answers → call B → B answers → bridge active
↓
gather A speech → translate → TTS to B → gather B speech → translate → TTS to A → loop
↓
hangup → hangup other caller → bridge ended
The client_state field on every Telnyx action carries the bridge ID and which side (a or b) the call belongs to. When a webhook fires, you decode the base64 state, look up the bridge, and know exactly which call leg you're handling. No session store — the call is self-describing.
The Language Code Bug
The original sample had all TTS and speech recognition hardcoded to language_code="en-US" — even for Spanish. The call "worked" (audio flowed, no errors) but the translation was useless:
- Spanish TTS with en-US: the TTS engine reads Spanish words with English phonemes. Unintelligible.
- Spanish STT with en-US: the STT engine listens for English sounds. Spanish speech recognition drops.
The fix — a language name → BCP-47 code mapping:
LANG_CODES = {
"english": "en-US", "spanish": "es-US", "french": "fr-FR",
"german": "de-DE", "italian": "it-IT", "portuguese": "pt-BR",
"hindi": "hi-IN", "arabic": "ar-SA", "chinese": "zh-CN",
"japanese": "ja-JP", "korean": "ko-KR", "russian": "ru-RU",
}
def lang_code(lang):
return LANG_CODES.get(lang.lower(), "en-US")
Now TTS uses the target language (Spanish text → es-US pronunciation) and STT uses the speaker's language (Spanish speech → es-US recognition).
The Translation Call
Uses Telnyx AI Inference — the OpenAI-compatible /v2/ai/chat/completions endpoint:
def translate(text, from_lang, to_lang):
resp = requests.post(INFERENCE_URL,
headers={"Authorization": f"Bearer {TELNYX_API_KEY}"},
json={
"model": AI_MODEL,
"messages": [
{"role": "system",
"content": f"Translate from {from_lang} to {to_lang}. "
"Return ONLY the translation, nothing else."},
{"role": "user", "content": text}
],
"max_tokens": 200, "temperature": 0.1
})
return resp.json()["choices"][0]["message"]["content"]
Temperature 0.1 for deterministic translations. 200 max tokens for conversational phrases. The model defaults to moonshotai/Kimi-K2.6 — swap to any model in the Telnyx catalog via the AI_MODEL env var.
No separate translation API account. The same Telnyx API key that makes the phone call does the translation.
Webhook Signature Verification
Telnyx signs every webhook with an Ed25519 key. The app verifies before trusting:
@app.route("/webhooks/voice", methods=["POST"])
def handle_voice():
try:
client.webhooks.unwrap(
request.get_data(as_text=True),
headers=dict(request.headers)
)
except Exception:
return jsonify({"error": "invalid signature"}), 401
Without this, anyone who knows your webhook URL can inject fake call events. The telnyx SDK's webhooks.unwrap() handles the Ed25519 verification.
The Call Loop
Each Telnyx event triggers the next action:
# Caller finished speaking → we have a transcript
elif event_type == "call.gather.ended" and bridge:
speech = p.get("speech", {}).get("result", "")
if speech and bridge.get("state") == "active":
from_lang = bridge[f"lang_{side}"]
to_lang = bridge["lang_b"] if side == "a" else bridge["lang_a"]
other_ccid = bridge["ccids"].get("b" if side == "a" else "a")
translated = translate(speech, from_lang, to_lang)
if other_ccid:
client.calls.actions.speak(other_ccid, payload=translated,
voice="female", language_code=lang_code(to_lang))
client.calls.actions.gather(ccid, input_type="speech",
language_code=lang_code(bridge[f"lang_{side}"]))
When either caller hangs up, the other is hung up too — a translation bridge with one caller is pointless:
elif event_type == "call.hangup" and bridge:
other_side = "b" if side == "a" else "a"
other_ccid = bridge.get("ccids", {}).get(other_side)
if other_ccid:
requests.post(f".../calls/{other_ccid}/actions/hangup", ...)
bridge["state"] = "ended"
Try It Yourself
git clone https://github.com/team-telnyx/telnyx-code-examples.git
cd telnyx-code-examples/ai-real-time-translation-bridge-python
cp .env.example .env # TELNYX_API_KEY, TELNYX_PUBLIC_KEY, BRIDGE_NUMBER, CONNECTION_ID
pip install -r requirements.txt
python app.py
Then:
ngrok http 5000
Point your Call Control Application webhook URL to https://<id>.ngrok.io/webhooks/voice in the Telnyx Portal.
Trigger a bridge:
curl -X POST http://localhost:5000/bridge \
-H "Content-Type: application/json" \
-d '{"number_a": "+13125550001", "lang_a": "English",
"number_b": "+5215550002", "lang_b": "Spanish"}'
Call from two phones. Speak in different languages. Hear real-time translation.
Key links:
- Repo: https://github.com/team-telnyx/telnyx-code-examples/tree/main/ai-real-time-translation-bridge-python
- Telnyx Portal: https://portal.telnyx.com
- Call Control docs: https://developers.telnyx.com/docs/voice/call-control
- AI Inference docs: https://developers.telnyx.com/docs/inference
Top comments (0)