Voice agents

Telnyx

Capture Telnyx Voice STT and TTS activity in Voxmith

Telnyx Voice uses the Voxmith voice webhook service. No Voxmith SDK import is required.

Data Sources

Telnyx data Who has it Voxmith capture
In-call transcription Telnyx webhook call.transcription Forward the webhook
REST/WebSocket STT text Your app receives the transcript Post kind: "stt"
TTS / speak text Your app sends the text to Telnyx Post kind: "tts" after Telnyx succeeds

Minimal Server Integration

Keep Telnyx pointed at your app. Add one small forwarder for final STT and one for TTS:

POST https://api.voxmith.com/api/voice/v1/events/telnyx
X-Org-Id: <voxmith-org-id>

Forward final transcription events:

{
  "kind": "stt",
  "data": {
    "event_type": "call.transcription",
    "payload": {
      "call_session_id": "call-session-id",
      "from": "+15551234567",
      "transcription_data": {
        "is_final": true,
        "transcript": "hello from Telnyx"
      }
    }
  }
}

Record TTS where your app already calls Telnyx speak/playback:

{
  "kind": "tts",
  "text": "Sure, I can help with billing.",
  "session_id": "call-session-id",
  "user_id": "caller-id",
  "mode": "in_call_playback"
}

Copy-Paste Middleware

Use one server-side helper. STT can run inside the incoming Telnyx webhook handler. TTS must run beside the existing Telnyx speak/playback call because that is where your backend has the bot text.

const voxmithVoiceUrl = "https://api.voxmith.com";
const voxmithOrgId = process.env.VOXMITH_ORG_ID;

async function sendVoxmithTelnyx(body: Record<string, unknown>) {
  await fetch(`${voxmithVoiceUrl}/api/voice/v1/events/telnyx`, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-Org-Id": voxmithOrgId!,
    },
    body: JSON.stringify(body),
  });
}

Forward final STT from your Telnyx webhook:

app.post("/webhooks/telnyx", async (req, res) => {
  const payload = req.body;
  const transcript = payload?.data?.payload?.transcription_data?.transcript;
  const isFinal = payload?.data?.payload?.transcription_data?.is_final !== false;
  const sessionId = payload?.data?.payload?.call_session_id;

  if (payload?.data?.event_type === "call.transcription" && isFinal && transcript) {
    await sendVoxmithTelnyx({
      kind: "stt",
      session_id: sessionId,
      event_id: payload.data.id,
      user_id: payload.data.payload.from,
      agent_name: "telnyx_voice",
      text: transcript,
    });
  }

  res.sendStatus(200);
});

Forward TTS where your product already speaks to the caller:

const replyText = await agentReply(userText);

await telnyx.calls.actions.speak(callControlId, { payload: replyText, voice: "female" });
await sendVoxmithTelnyx({
  kind: "tts",
  session_id: callSessionId,
  event_id: crypto.randomUUID(),
  user_id: callerId,
  agent_name: "telnyx_voice",
  text: replyText,
});

Telnyx SDK Users

Using the Telnyx Node or Python SDK does not replace the STT webhook. Keep webhook_url configured on the call so final call.transcription events still reach your backend. Then wrap the SDK speak call to record TTS.

Node.js:

import Telnyx from "telnyx";
import crypto from "node:crypto";

const telnyx = new Telnyx({ apiKey: process.env.TELNYX_API_KEY });

async function speakWithVoxmith({
  callControlId,
  callSessionId,
  callerId,
  text,
}: {
  callControlId: string;
  callSessionId: string;
  callerId: string;
  text: string;
}) {
  const eventId = crypto.randomUUID();

  await telnyx.calls.actions.speak(callControlId, {
    payload: text,
    voice: "female",
    command_id: eventId,
  });

  await sendVoxmithTelnyx({
    kind: "tts",
    session_id: callSessionId,
    event_id: eventId,
    user_id: callerId,
    agent_name: "telnyx_voice",
    text,
  });
}

Python:

import os
import uuid
import json
import urllib.request

from telnyx import Telnyx

telnyx = Telnyx(api_key=os.environ.get("TELNYX_API_KEY"))


def send_voxmith_telnyx(body: dict) -> None:
    request = urllib.request.Request(
        "https://api.voxmith.com/api/voice/v1/events/telnyx",
        data=json.dumps(body).encode("utf-8"),
        headers={
            "Content-Type": "application/json",
            "X-Org-Id": os.environ["VOXMITH_ORG_ID"],
        },
        method="POST",
    )
    with urllib.request.urlopen(request, timeout=5):
        pass


def speak_with_voxmith(call_control_id: str, call_session_id: str, caller_id: str, text: str) -> None:
    event_id = str(uuid.uuid4())

    telnyx.calls.actions.speak(
        call_control_id=call_control_id,
        payload=text,
        voice="female",
        command_id=event_id,
    )

    send_voxmith_telnyx({
        "kind": "tts",
        "session_id": call_session_id,
        "event_id": event_id,
        "user_id": caller_id,
        "agent_name": "telnyx_voice",
        "text": text,
    })

Use the same session_id for STT and TTS events from the same call/session. It can be the Telnyx conversation_id, call_session_id, or call_control_id; Voxmith turns non-UUID Telnyx IDs into stable dashboard UUIDs and keeps the original ID in metadata. Pass a stable event_id when your middleware can retry the same STT/TTS event.

The Voxmith voice service stores final STT in memory for up to 10 minutes until the next TTS event for the same session_id. That TTS write becomes one dashboard event with user speech in args and bot speech in result. If a TTS event arrives without a queued STT, or after that queue expires, Voxmith records it as output-only.

Troubleshooting

  • Only bot messages appear: TTS is arriving before final STT, more than 10 minutes after STT, or with a different session_id.
  • No user input appears: you are forwarding partial STT only. Voxmith ignores STT where final or is_final is false.
  • TTS appears in a separate conversation: pass the same Telnyx conversation or call ID as session_id.
  • The accepted session_id looks different from the Telnyx ID: use the returned conversation_id in dashboard/debug links. The original Telnyx ID is stored as metadata.
  • No events appear: confirm X-Org-Id is set and your server can reach the Voxmith voice service.
  • Missing bot output: you are forwarding STT only. Post the TTS text after the Telnyx speak/playback request succeeds.

Telnyx references

Next steps

  • Conversations: review complete Telnyx calls.
  • Events: inspect captured speech and agent responses.
  • Intents: understand what callers wanted.