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:
Go to Call Control > TeXML Applications
Click Create TeXML Application
Configure:
Name : Kallglot Translation
Voice URL : https://your-app.com/webhooks/telnyx/voice
Voice Method : POST
Note the Application ID
2. Assign Phone Number
Go to Numbers > My Numbers
Select your number
Under Voice , select your TeXML application
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
Feature Telnyx Twilio Pricing Generally lower Higher Global coverage 100+ countries 180+ countries SIP Trunking Native support Via Elastic SIP Media streams Yes Yes API style REST + WebSocket REST + TwiML Number porting Yes Yes
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
Use Call Control for complex flows
For multi-step call flows (IVR, transfers), use the Call Control API instead of TeXML for more flexibility.
Handle network issues gracefully
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
Verify the TeXML application is assigned to your number
Check webhook URL is accessible (use ngrok for testing)
Verify TeXML syntax is valid
No audio translation
Check track="both_tracks" is set in Stream element
Verify Kallglot session was created successfully
Check WebSocket connection in stream logs
High latency
Check region configuration
Verify network path between Telnyx and your server
Consider using Telnyx’s edge locations closer to your users