Connect inbound phone calls to an Ultravox agent in under 6 minutes.
Steal this code
index.js
.Install twilio node library
twilio
via pnpm | npm install twilio
.Install Express.js
express
via pnpm | npm install express
.Configure variables
Start the server
node index.js
.Expose the endpoint
ngrok http 3000
.Configure incoming call webhook in Twilio
your_ngrok_url/incoming
.Call your Twilio number
import express from 'express';
import https from 'https';
import twilio from 'twilio';
const app = express();
const port = 3000;
// ------------------------------------------------------------
// Step 1: Configure Ultravox API key
//
// Optional: Modify the system prompt
// ------------------------------------------------------------
const ULTRAVOX_API_KEY = 'your_ultravox_api_key_here';
const SYSTEM_PROMPT = 'Your name is Steve. You are receiving a phone call. Ask them their name and see how they are doing.';
// Ultravox configuration that will be used to create the call
const ULTRAVOX_CALL_CONFIG = {
systemPrompt: SYSTEM_PROMPT,
model: 'fixie-ai/ultravox',
voice: 'Mark',
temperature: 0.3,
medium: { "twilio": {} }
};
// Ensure required configuration vars are set
function validateConfiguration() {
const requiredConfig = [
{ name: 'ULTRAVOX_API_KEY', value: ULTRAVOX_API_KEY, pattern: /^[a-zA-Z0-9]{8}\.[a-zA-Z0-9]{32}$/ }
];
const errors = [];
for (const config of requiredConfig) {
if (!config.value || config.value.includes('your_') || config.value.includes('_here')) {
errors.push(`❌ ${config.name} is not set or still contains placeholder text`);
} else if (config.pattern && !config.pattern.test(config.value)) {
errors.push(`❌ ${config.name} format appears invalid`);
}
}
if (errors.length > 0) {
console.error('🚨 Configuration Error(s):');
errors.forEach(error => console.error(` ${error}`));
console.error('\n💡 Please update the configuration variables at the top of this file:');
console.error(' • ULTRAVOX_API_KEY should be 8 chars + period + 32 chars (e.g., Zk9Ht7Lm.wX7pN9fM3kLj6tRq2bGhA8yE5cZvD4sT)');
return false;
}
console.log('✅ Configuration validation passed!');
return true;
}
// Create Ultravox call and get join URL
async function createUltravoxCall() {
const ULTRAVOX_API_URL = 'https://api.ultravox.ai/api/calls';
const request = https.request(ULTRAVOX_API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': ULTRAVOX_API_KEY
}
});
return new Promise((resolve, reject) => {
let data = '';
request.on('response', (response) => {
response.on('data', chunk => data += chunk);
response.on('end', () => {
try {
const parsedData = JSON.parse(data);
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve(parsedData);
} else {
reject(new Error(`Ultravox API error (${response.statusCode}): ${data}`));
}
} catch (parseError) {
reject(new Error(`Failed to parse Ultravox response: ${data}`));
}
});
});
request.on('error', (error) => {
reject(new Error(`Network error calling Ultravox: ${error.message}`));
});
request.write(JSON.stringify(ULTRAVOX_CALL_CONFIG));
request.end();
});
}
// Handle incoming calls from Twilio
// Note: We have to expose this endpoint publicly (e.g. using ngrok in dev)
// and set as incoming call webhook in Twilio
app.post('/incoming', async (req, res) => {
try {
console.log('📞 Incoming call received');
// Validate configuration on each call
if (!validateConfiguration()) {
console.error('💥 Configuration validation failed for incoming call');
const twiml = new twilio.twiml.VoiceResponse();
twiml.say('Sorry, there was a configuration error. Please contact support.');
res.type('text/xml');
res.send(twiml.toString());
return;
}
console.log('🤖 Creating Ultravox call...');
const response = await createUltravoxCall();
if (!response.joinUrl) {
throw new Error('No joinUrl received from Ultravox API');
}
console.log('✅ Got Ultravox joinUrl:', response.joinUrl);
const twiml = new twilio.twiml.VoiceResponse();
const connect = twiml.connect();
connect.stream({
url: response.joinUrl,
name: 'ultravox'
});
const twimlString = twiml.toString();
console.log('📋 Sending TwiML response to Twilio');
res.type('text/xml');
res.send(twimlString);
} catch (error) {
console.error('💥 Error handling incoming call:');
if (error.message.includes('Ultravox')) {
console.error(' 🤖 Ultravox API issue - check your API key and try again');
} else if (error.message.includes('Authentication')) {
console.error(' 🔐 Authentication failed - check your Ultravox API key');
} else {
console.error(` ${error.message}`);
}
console.error('\n🔍 Troubleshooting tips:');
console.error(' • Double-check your ULTRAVOX_API_KEY configuration');
console.error(' • Verify your Ultravox API key is valid and active');
console.error(' • Check your internet connection');
const twiml = new twilio.twiml.VoiceResponse();
twiml.say('Sorry, there was an error connecting your call. Please try again later.');
res.type('text/xml');
res.send(twiml.toString());
}
});
// Starts Express.js server to expose the /incoming route
function startServer() {
console.log('🚀 Starting Inbound Ultravox Voice AI Phone Server...\n');
// Check configuration on startup but don't exit - just warn
const isConfigValid = validateConfiguration();
if (!isConfigValid) {
console.warn('⚠️ Server starting with invalid configuration.');
console.warn('📞 Calls will fail until configuration is updated.\n');
}
app.listen(port, () => {
console.log(`🎉 Server running successfully on port ${port}`);
console.log(`📞 Ready to handle incoming calls at POST /incoming`);
console.log(`🌐 Webhook URL: http://your-server:${port}/incoming`);
console.log('\n💡 Setup reminder:');
console.log(' • Configure your Twilio phone number webhook to point to this server');
console.log(' • Make sure this server is accessible from the internet (consider using ngrok for testing)');
if (!isConfigValid) {
console.log(' • ⚠️ Update your ULTRAVOX_API_KEY before handling calls');
}
});
}
startServer();