Skip to main content

Telnyx Integration

This guide walks you through integrating Kallglot with Telnyx for real-time voice translation.

Prerequisites

  • A Telnyx account with a phone number
  • A TeXML application configured
  • A Kallglot API key
  • A web server to receive webhooks

Architecture

Setup Steps

1. Create a TeXML Application

In the Telnyx Portal:
  1. Go to Call Control > TeXML Applications
  2. Click Create TeXML Application
  3. Configure:
    • Name: Kallglot Translation
    • Voice URL: https://your-app.com/webhooks/telnyx/voice
    • Voice Method: POST
  4. Note the Application ID

2. Assign Phone Number

  1. Go to Numbers > My Numbers
  2. Select your number
  3. Under Voice, select your TeXML application
  4. Save

3. Create the Webhook Handler

Kallglot does not currently provide official SDKs. The helper functions in these examples, such as createKallglotSession(...), represent standard HTTP requests to the API.
import express from 'express';

const app = express();

app.post('/webhooks/telnyx/voice', express.json(), async (req, res) => {
  const event = req.body.data;
  const { call_control_id, from, to, direction } = event.payload;

  try {
    // Create a Kallglot session
    const session = await createKallglotSession({
      mode: 'bidirectional_translation',
      source_language: 'de',
      target_language: 'en',
      provider: {
        type: 'telnyx',
        call_control_id: call_control_id
      },
      metadata: {
        from,
        to,
        direction
      }
    });

    // Generate TeXML to connect the call to Kallglot
    const texml = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say language="de-DE">Willkommen. Ihr Anruf wird übersetzt.</Say>
  <Connect>
    <Stream url="${session.stream.url}" track="both_tracks">
      <Parameter name="token" value="${session.stream.token}"/>
      <Parameter name="session_id" value="${session.id}"/>
    </Stream>
  </Connect>
</Response>`;

    res.type('text/xml').send(texml);

  } catch (error) {
    console.error('Failed to create session:', error);

    // Fallback without translation
    const texml = `<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say>Translation is temporarily unavailable. Connecting you now.</Say>
  <Dial>+14155550123</Dial>
</Response>`;

    res.type('text/xml').send(texml);
  }
});

app.listen(3000);

4. Handle Call Events

Set up status callbacks for call lifecycle events:
app.post('/webhooks/telnyx/status', express.json(), async (req, res) => {
  const event = req.body.data;
  const eventType = event.event_type;
  const { call_control_id } = event.payload;

  switch (eventType) {
    case 'call.hangup':
      // Call ended - end the Kallglot session
      const sessionId = await getSessionIdForCall(call_control_id);
      if (sessionId) {
        await endKallglotSession(sessionId, {
          reason: event.payload.hangup_cause
        });
      }
      break;

    case 'call.answered':
      console.log('Call answered');
      break;

    case 'streaming.started':
      console.log('Media streaming started');
      break;

    case 'streaming.stopped':
      console.log('Media streaming stopped');
      break;
  }

  res.status(200).send('OK');
});

Advanced Configurations

Outbound Calls with Call Control API

For more control, use Telnyx’s Call Control API:
import Telnyx from 'telnyx';

const telnyx = new Telnyx(process.env.TELNYX_API_KEY);

async function makeOutboundCall(customerPhone, fromNumber) {
  // 1. Create Kallglot session
  const session = await createKallglotSession({
    mode: 'bidirectional_translation',
    source_language: 'de',
    target_language: 'en'
  });

  // 2. Store session for webhook lookup
  await db.pendingCalls.insert({
    session_id: session.id,
    to: customerPhone
  });

  // 3. Initiate call via Telnyx
  const call = await telnyx.calls.create({
    connection_id: process.env.TELNYX_CONNECTION_ID,
    to: customerPhone,
    from: fromNumber,
    webhook_url: 'https://your-app.com/webhooks/telnyx/outbound'
  });

  return { session, call };
}

// Webhook for outbound call events
app.post('/webhooks/telnyx/outbound', express.json(), async (req, res) => {
  const event = req.body.data;

  if (event.event_type === 'call.initiated') {
    // Look up session for this call
    const pending = await db.pendingCalls.findOne({
      to: event.payload.to
    });

    if (pending) {
      const session = await retrieveKallglotSession(pending.session_id);

      // Answer and start streaming
      await telnyx.calls.answer(event.payload.call_control_id);

      await telnyx.calls.startStream(event.payload.call_control_id, {
        stream_url: session.stream.url,
        stream_track: 'both_tracks'
      });
    }
  }

  res.status(200).send('OK');
});

Using SIP Trunking

For SIP-based integration with your PBX:
const session = await createKallglotSession({
  mode: 'bidirectional_translation',
  source_language: 'de',
  target_language: 'en',
  provider: {
    type: 'telnyx',
    sip_uri: 'sip:agent@your-pbx.com'
  }
});

// Configure your PBX to route calls to:
// sip:${session.id}@sip.telnyx.com

Recording Configuration

Configure call recording in TeXML:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Record
    recordingStatusCallback="https://your-app.com/webhooks/telnyx/recording"
    recordingStatusCallbackEvent="completed"
    transcribe="false"
  />
  <Connect>
    <Stream url="${session.stream.url}" track="both_tracks">
      <Parameter name="token" value="${session.stream.token}"/>
    </Stream>
  </Connect>
</Response>

Telnyx vs Twilio Comparison

FeatureTelnyxTwilio
PricingGenerally lowerHigher
Global coverage100+ countries180+ countries
SIP TrunkingNative supportVia Elastic SIP
Media streamsYesYes
API styleREST + WebSocketREST + TwiML
Number portingYesYes

Error Handling

Handle API Errors

app.post('/webhooks/telnyx/voice', async (req, res) => {
  try {
    const session = await createKallglotSession({
      // ...
    });

    // Success - return TeXML
    res.type('text/xml').send(buildTeXML(session));

  } catch (error) {
    console.error('Error:', error);

    // Check error type
    if (error.code === 'rate_limit_exceeded') {
      // Return busy signal
      res.type('text/xml').send(`
        <Response>
          <Reject reason="busy"/>
        </Response>
      `);
    } else {
      // Fallback to non-translated call
      res.type('text/xml').send(`
        <Response>
          <Say>Translation unavailable. Please hold.</Say>
          <Dial>+14155550123</Dial>
        </Response>
      `);
    }
  }
});

Handle Webhook Signature Verification

Telnyx signs webhooks with a signature you should verify:
import crypto from 'crypto';

function verifyTelnyxSignature(req) {
  const signature = req.headers['telnyx-signature-ed25519'];
  const timestamp = req.headers['telnyx-timestamp'];
  const publicKey = process.env.TELNYX_PUBLIC_KEY;

  const signedPayload = `${timestamp}|${JSON.stringify(req.body)}`;

  // Verify using Ed25519
  return crypto.verify(
    null,
    Buffer.from(signedPayload),
    publicKey,
    Buffer.from(signature, 'base64')
  );
}

Best Practices

For multi-step call flows (IVR, transfers), use the Call Control API instead of TeXML for more flexibility.
Telnyx webhooks may timeout. Respond quickly and process asynchronously:
app.post('/webhooks/telnyx/voice', async (req, res) => {
  // Respond immediately
  res.type('text/xml').send(buildQuickResponse());

  // Process in background
  setImmediate(async () => {
    await processCall(req.body);
  });
});
Configure Telnyx connections in regions close to your customers to minimize latency.

Troubleshooting

Calls not connecting

  1. Verify the TeXML application is assigned to your number
  2. Check webhook URL is accessible (use ngrok for testing)
  3. Verify TeXML syntax is valid

No audio translation

  1. Check track="both_tracks" is set in Stream element
  2. Verify Kallglot session was created successfully
  3. Check WebSocket connection in stream logs

High latency

  1. Check region configuration
  2. Verify network path between Telnyx and your server
  3. Consider using Telnyx’s edge locations closer to your users