How to Authenticate and POST to /v1/call Endpoint in Retell AI for Outbound Twilio Calls

Discover how to effectively authenticate and use the /v1/call endpoint in Retell AI for seamless outbound Twilio calls. Learn practical steps now!

Misal Azeem
Misal Azeem

Voice AI Engineer & Creator

How to Authenticate and POST to /v1/call Endpoint in Retell AI for Outbound Twilio Calls

How to Authenticate and POST to /v1/call Endpoint in Retell AI for Outbound Twilio Calls

TL;DR

Most outbound call integrations fail at authentication or payload validation. Here's what breaks: missing Bearer tokens, malformed E.164 phone numbers, and agent override misconfiguration. This guide shows you how to authenticate against Retell AI's /v1/call endpoint, structure the request payload correctly, and handle Twilio number registration so your outbound calls actually connect—not timeout or reject.

Prerequisites

API Keys & Credentials

You need a Retell AI API key (generate from your Retell dashboard under Settings → API Keys). Store it in .env as RETELL_API_KEY. You'll also need a Twilio Account SID and Auth Token (found in Twilio Console → Account Info), though Retell handles the Twilio integration directly—you don't call Twilio's API yourself for outbound calls.

System Requirements

Node.js 16+ with npm or yarn. Install the Retell SDK (npm install retell-sdk) or use raw HTTP with fetch/axios. For testing webhooks locally, install ngrok (npm install -g ngrok) to expose your server.

Phone Numbers

Prepare E.164 formatted phone numbers for the recipient (e.g., +14155552671). Your Twilio account must have verified outbound numbers or a purchased phone number assigned.

Server Setup

A basic Node.js/Express server running on localhost:3000 (or your preferred port) to receive webhooks from Retell. HTTPS is required for production webhook endpoints.

Twilio: Get Twilio Voice API → Get Twilio

Step-by-Step Tutorial

Configuration & Setup

Most outbound call failures happen in the first 30 seconds because developers skip authentication validation. Retell AI uses Bearer token auth, and Twilio requires account SID + auth token. Both must be validated BEFORE you attempt a call.

Server Requirements:

  • Node.js 18+ (for native fetch)
  • Express or Fastify for webhook handling
  • Environment variables for secrets (NEVER hardcode)
javascript
// server.js - Production-grade setup
import express from 'express';
import crypto from 'crypto';

const app = express();
app.use(express.json());

// Validate environment on startup (fail fast)
const RETELL_API_KEY = process.env.RETELL_API_KEY;
const TWILIO_ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID;
const TWILIO_AUTH_TOKEN = process.env.TWILIO_AUTH_TOKEN;

if (!RETELL_API_KEY || !TWILIO_ACCOUNT_SID || !TWILIO_AUTH_TOKEN) {
  throw new Error('Missing required environment variables');
}

// Health check endpoint (monitors use this)
app.get('/health', (req, res) => {
  res.json({ status: 'ok', timestamp: Date.now() });
});

app.listen(3000, () => console.log('Server ready on port 3000'));

Architecture & Flow

Critical distinction: Retell AI handles the voice AI layer. Twilio handles the telephony layer. Your server orchestrates both.

Call Flow (Outbound):

  1. Your server initiates call via Retell AI API
  2. Retell AI provisions agent and returns call metadata
  3. Retell AI connects to Twilio via SIP trunk
  4. Twilio dials the target phone number
  5. On answer, Retell AI streams audio bidirectionally
  6. Webhooks fire for call events (started, ended, analyzed)

What breaks in production: Developers try to use Twilio's REST API directly for voice AI calls. This creates a split-brain scenario where Twilio and Retell AI both think they own the call state. Use Retell AI's /v1/call endpoint ONLY.

Step-by-Step Implementation

1. Create Retell AI Assistant

Before making calls, you need an assistant ID. This defines the AI's behavior, voice, and LLM configuration.

javascript
// createAssistant.js - Run once to get assistant_id
async function createAssistant() {
  const assistantConfig = {
    name: "Outbound Sales Agent",
    model: {
      provider: "openai",
      model: "gpt-4-turbo",
      temperature: 0.7,
      max_tokens: 500
    },
    voice: {
      provider: "elevenlabs",
      voice_id: "21m00Tcm4TlvDq8ikWAM", // Rachel voice
      speed: 1.0,
      stability: 0.5,
      similarity_boost: 0.75
    },
    transcriber: {
      provider: "deepgram",
      model: "nova-2",
      language: "en-US"
    },
    response_engine: {
      type: "retell-llm",
      llm_websocket_url: "wss://your-server.com/llm-websocket"
    }
  };

  try {
    const response = await fetch('https://api.retell.ai/v1/assistant', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${RETELL_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(assistantConfig)
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Assistant creation failed: ${error.message}`);
    }

    const assistant = await response.json();
    console.log('Assistant ID:', assistant.assistant_id);
    return assistant.assistant_id;
  } catch (error) {
    console.error('Failed to create assistant:', error);
    throw error;
  }
}

2. Initiate Outbound Call

Critical: Phone numbers MUST be in E.164 format (+1234567890). Missing the + prefix causes silent failures where Twilio accepts the request but never dials.

javascript
// initiateCall.js - Production call initiation
async function makeOutboundCall(toNumber, assistantId) {
  // Validate E.164 format (prevents 90% of dial failures)
  if (!toNumber.match(/^\+[1-9]\d{1,14}$/)) {
    throw new Error(`Invalid E.164 format: ${toNumber}`);
  }

  const callConfig = {
    from_number: process.env.TWILIO_FROM_NUMBER, // Your Twilio number
    to_number: toNumber,
    assistant_id: assistantId,
    metadata: {
      campaign_id: "Q1_2024_outreach",
      lead_source: "webinar_signup"
    },
    retell_llm_dynamic_variables: {
      customer_name: "John Doe",
      account_balance: "$1,234.56"
    }
  };

  try {
    const response = await fetch('https://api.retell.ai/v1/call', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${RETELL_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(callConfig)
    });

    if (!response.ok) {
      const error = await response.json();
      // Common error codes: 401 (bad auth), 400 (invalid number), 429 (rate limit)
      throw new Error(`Call failed: ${response.status} - ${error.message}`);
    }

    const call = await response.json();
    console.log('Call initiated:', call.call_id);
    return call;
  } catch (error) {
    console.error('Call initiation error:', error);
    throw error;
  }
}

3. Handle Webhook Events

Retell AI sends webhooks for call lifecycle events. You MUST validate webhook signatures to prevent spoofing attacks.

javascript
// webhookHandler.js - Secure webhook processing
app.post('/webhook/retell', async (req, res) => {
  const signature = req.headers['x-retell-signature'];
  const payload = JSON.stringify(req.body);
  
  // Validate webhook signature (CRITICAL for security)
  const expectedSignature = crypto
    .createHmac('sha256', process.env.RETELL_WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
  
  if (signature !== expectedSignature) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Unauthorized');
  }

  const event = req.body;
  
  // Handle different event types
  switch (event.event) {
    case 'call_started':
      console.log(`Call ${event.call_id} started`);
      // Update CRM, start recording, etc.
      break;
      
    case 'call_ended':
      console.log(`Call ${event.call_id} ended. Duration: ${event.call_duration}s`);
      // Log to analytics, trigger follow-up workflows
      break;
      
    case 'call_analyzed':
      // Contains transcript, sentiment, custom analysis
      console.log('Call analysis:', event.call_analysis);
      break;
      
    default:
      console.log('Unknown event type:', event.event);
  }
  
  // ALWAYS respond 200 immediately (prevents retries)
  res.status(200).send('OK');
});

Error Handling & Edge Cases

Race Condition: If you call /v1/call twice with the same metadata within 5 seconds, Retell AI may deduplicate and return the same call_id. Add a unique `

System Diagram

Call flow showing how Retell AI handles user input, webhook events, and responses.

mermaid
sequenceDiagram
    participant User
    participant RetellAI
    participant SpeechEngine
    participant NLPProcessor
    participant ErrorHandler
    User->>RetellAI: Initiates conversation
    RetellAI->>SpeechEngine: Capture audio
    SpeechEngine->>RetellAI: Audio stream
    RetellAI->>NLPProcessor: Send audio for transcription
    NLPProcessor->>RetellAI: Transcription text
    RetellAI->>User: Provide response
    Note over User,RetellAI: User feedback loop
    User->>RetellAI: Provides feedback
    RetellAI->>ErrorHandler: Check for errors
    ErrorHandler->>RetellAI: Error status
    alt Error detected
        RetellAI->>User: Notify error
    else No error
        RetellAI->>User: Continue interaction
    end
    User->>RetellAI: Ends conversation
    RetellAI->>SpeechEngine: Stop audio capture
    SpeechEngine->>RetellAI: Confirmation of stop

Testing & Validation

Advertisement

Local Testing with ngrok

Most outbound call failures happen because webhooks can't reach your local server. Retell AI needs a public URL to send call events—localhost won't cut it.

javascript
// Start ngrok tunnel (run in terminal)
// ngrok http 3000

// Update webhook URL in your call config
const callConfig = {
  agent_id: assistant.agent_id,
  metadata: {
    campaign_id: "test_001",
    environment: "development"
  },
  retell_llm_dynamic_variables: {
    customer_name: "Test User"
  },
  from_number: "+15551234567",
  to_number: "+15559876543"
};

// Test the call initiation
const response = await fetch('https://api.retell.ai/v1/call', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${RETELL_API_KEY}`,
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(callConfig)
});

if (!response.ok) {
  const error = await response.json();
  console.error('Call failed:', error.message, error.codes);
  // Common: 401 (bad API key), 400 (invalid phone format), 403 (insufficient credits)
}

const call = await response.json();
console.log('Call initiated:', call.call_id, call.status);

Real failure mode: If ngrok disconnects mid-call, Retell AI can't send the call-ended event. Your session cleanup never fires. Set a 5-minute TTL on all call sessions to prevent memory leaks.

Webhook Validation

Validate webhook signatures to prevent replay attacks. Retell AI signs payloads with HMAC-SHA256.

javascript
const crypto = require('crypto');

app.post('/webhook/retell', (req, res) => {
  const signature = req.headers['x-retell-signature'];
  const payload = JSON.stringify(req.body);
  
  const expectedSignature = crypto
    .createHmac('sha256', process.env.RETELL_WEBHOOK_SECRET)
    .update(payload)
    .digest('hex');
  
  if (signature !== expectedSignature) {
    console.error('Invalid webhook signature');
    return res.status(401).send('Unauthorized');
  }
  
  const event = req.body;
  console.log('Valid event:', event.type, event.call_id);
  
  // Process event based on type
  if (event.type === 'call_started') {
    // Initialize call state
  } else if (event.type === 'call_ended') {
    // Cleanup and log analysis
    console.log('Call analysis:', event.analysis);
  }
  
  res.status(200).send('OK');
});

Production gotcha: Webhook timeouts after 5 seconds cause Retell AI to retry 3 times with exponential backoff. If your handler does heavy processing (CRM updates, transcription storage), return 200 immediately and queue the work asynchronously. Otherwise, you'll get duplicate events and race conditions on call state updates.

Real-World Example

Barge-In Scenario

Most outbound call systems break when the customer interrupts the agent mid-pitch. Here's what actually happens in production:

Your Retell agent starts reading a 30-second script about account balance. At 8 seconds, the customer says "Wait, what's my balance?" The agent keeps talking for another 2 seconds (TTS buffer lag), then finally stops. By then, the customer has repeated the question twice, and you've burned 4 extra seconds of call time.

javascript
// Production barge-in handler with buffer flush
app.post('/webhook/retell', async (req, res) => {
  const event = req.body;
  
  if (event.event === 'speech_started') {
    // Customer started speaking - IMMEDIATELY cancel TTS
    const callId = event.call.call_id;
    
    try {
      await fetch(`https://api.retell.ai/v2/stop-utterance`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.RETELL_API_KEY}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ 
          call_id: callId,
          utterance_id: event.call.last_utterance_id // Critical: stop SPECIFIC utterance
        })
      });
      
      // Flush audio buffer to prevent stale audio playback
      console.log(`[${callId}] Barge-in detected - TTS cancelled at ${Date.now()}`);
    } catch (error) {
      console.error('Failed to cancel TTS:', error);
      // Fallback: agent will finish sentence (bad UX but won't crash)
    }
  }
  
  res.status(200).send();
});

Why this breaks without proper handling: Retell's TTS buffer holds 1-3 seconds of pre-generated audio. If you don't explicitly cancel the utterance, the agent talks over the customer, creating the "robotic conversation" effect that tanks CSAT scores.

Event Logs

Real production webhook payload when barge-in happens:

javascript
// Event 1: Agent starts speaking (t=0ms)
{
  "event": "agent_start_talking",
  "call": {
    "call_id": "call_abc123",
    "last_utterance_id": "utt_xyz789"
  },
  "timestamp": 1704067200000
}

// Event 2: Customer interrupts (t=1200ms)
{
  "event": "speech_started", // THIS is your trigger
  "call": {
    "call_id": "call_abc123",
    "transcript": "" // Empty - speech detected but not transcribed yet
  },
  "timestamp": 1704067201200
}

// Event 3: Partial transcript arrives (t=1450ms)
{
  "event": "transcript",
  "call": {
    "call_id": "call_abc123",
    "transcript": "Wait what's my",
    "is_final": false
  },
  "timestamp": 1704067201450
}

Critical timing issue: You have ~250ms between speech_started and the first partial transcript. If you wait for the transcript to decide whether to interrupt, you've already lost 250ms + TTS cancellation latency (100-200ms). Total lag: 350-450ms of the agent talking over the customer.

Edge Cases

False positive from background noise: At default VAD threshold (0.5), a dog barking triggers speech_started. Your agent stops mid-sentence, waits 2 seconds for speech, hears nothing, then awkwardly resumes. Solution: Increase VAD threshold to 0.65 for noisy environments, or implement a 300ms confirmation window before cancelling TTS.

Rapid-fire interruptions: Customer says "Wait... no wait... actually..." (3 interruptions in 5 seconds). Without state tracking, each speech_started event fires a new TTS cancellation request, creating a race condition where the agent never finishes a sentence. Fix: Track isProcessing flag and ignore subsequent interruptions until current turn completes.

javascript
// Race condition guard for multiple interruptions
let isProcessing = false;

if (event.event === 'speech_started' && !isProcessing) {
  isProcessing = true;
  await cancelTTS(event.call.call_id);
  
  // Reset flag after turn completes (use 'agent_stop_talking' event)
  setTimeout(() => { isProcessing = false; }, 5000); // 5s timeout fallback
}

Common Issues & Fixes

Most outbound call failures stem from three production problems: authentication token leaks, E.164 format violations, and race conditions during concurrent call creation. Here's what breaks in production.

Authentication Token Exposure

Problem: Hardcoded API keys in client-side code leak credentials. Retell AI rejects requests with 401 Unauthorized when tokens are rotated or exposed.

Fix: Store credentials server-side only. Never expose RETELL_API_KEY in frontend JavaScript.

javascript
// Server-side authentication wrapper
async function authenticatedCallRequest(callConfig) {
  if (!process.env.RETELL_API_KEY) {
    throw new Error('RETELL_API_KEY not configured');
  }
  
  try {
    const response = await fetch('https://api.retellai.com/v1/call', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.RETELL_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(callConfig)
    });
    
    if (response.status === 401) {
      throw new Error('Invalid API key - check environment variables');
    }
    
    return await response.json();
  } catch (error) {
    console.error('Auth failed:', error.message);
    throw error;
  }
}

E.164 Phone Number Validation

Problem: Twilio rejects calls when from_number or to_number lack country codes. Error: Invalid phone number format.

Fix: Validate E.164 format before POST. Must start with + and country code (e.g., +14155551234).

javascript
function validateE164(phoneNumber) {
  const e164Regex = /^\+[1-9]\d{1,14}$/;
  if (!e164Regex.test(phoneNumber)) {
    throw new Error(`Invalid E.164 format: ${phoneNumber}`);
  }
  return phoneNumber;
}

// Use before call creation
const callConfig = {
  from_number: validateE164('+14155551234'),
  to_number: validateE164('+16505551234'),
  assistant_id: assistant.ID
};

Race Conditions in Concurrent Calls

Problem: Multiple simultaneous POST requests create duplicate calls when isProcessing flag isn't atomic. Results in double-billing and confused call state.

Fix: Implement request queuing with unique callId tracking.

javascript
const activeCallIds = new Set();

async function makeOutboundCall(callConfig) {
  const callId = `${callConfig.to_number}-${Date.now()}`;
  
  if (activeCallIds.has(callId)) {
    throw new Error('Call already in progress');
  }
  
  activeCallIds.add(callId);
  isProcessing = true;
  
  try {
    const call = await authenticatedCallRequest(callConfig);
    return call;
  } finally {
    activeCallIds.delete(callId);
    isProcessing = false;
  }
}

Production tip: Set isProcessing = false in finally block to prevent deadlocks when requests fail mid-flight.

Complete Working Example

Most developers hit authentication errors or malformed payloads when first calling /v1/call. Here's the full production server that handles Retell AI authentication, Twilio integration, and outbound call orchestration in one copy-paste block.

Full Server Code

This Express server combines assistant creation, authenticated API calls, webhook validation, and E.164 phone number validation. All routes are production-ready with error handling and security checks.

javascript
const express = require('express');
const crypto = require('crypto');
const app = express();

app.use(express.json());

// Environment variables (set these in .env)
const RETELL_API_KEY = process.env.RETELL_API_KEY;
const TWILIO_ACCOUNT_SID = process.env.TWILIO_ACCOUNT_SID;
const TWILIO_AUTH_TOKEN = process.env.TWILIO_AUTH_TOKEN;
const RETELL_WEBHOOK_SECRET = process.env.RETELL_WEBHOOK_SECRET;

// E.164 validation regex
const e164Regex = /^\+[1-9]\d{1,14}$/;

function validateE164(phoneNumber) {
  if (!e164Regex.test(phoneNumber)) {
    throw new Error(`Invalid E.164 format: ${phoneNumber}. Must start with + and country code.`);
  }
  return phoneNumber;
}

// Track active calls to prevent duplicates
const activeCallIds = new Set();
let isProcessing = false;

// Create assistant with Retell AI
async function createAssistant() {
  const assistantConfig = {
    name: "Outbound Sales Agent",
    model: {
      provider: "openai",
      model: "gpt-4",
      temperature: 0.7,
      max_tokens: 150
    },
    voice: {
      provider: "elevenlabs",
      voice_id: "21m00Tcm4TlvDq8ikWAM",
      speed: 1.0,
      stability: 0.5,
      similarity_boost: 0.75
    },
    transcriber: {
      provider: "deepgram",
      language: "en-US"
    },
    response_engine: {
      type: "retell-llm",
      llm_websocket_url: "wss://your-server.com/llm-websocket"
    }
  };

  try {
    const response = await fetch('https://api.retellai.com/v1/assistant', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${RETELL_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(assistantConfig)
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Assistant creation failed: ${error.message}`);
    }

    const assistant = await response.json();
    return assistant.assistant_id;
  } catch (error) {
    console.error('Assistant creation error:', error);
    throw error;
  }
}

// Make authenticated outbound call
async function makeOutboundCall(assistantId, toNumber, fromNumber) {
  validateE164(toNumber);
  validateE164(fromNumber);

  const callConfig = {
    assistant_id: assistantId,
    from_number: fromNumber,
    to_number: toNumber,
    metadata: {
      campaign_id: "spring-2024-promo",
      lead_source: "website-form"
    },
    retell_llm_dynamic_variables: {
      customer_name: "John Doe",
      account_balance: "$1,234.56"
    }
  };

  try {
    const response = await fetch('https://api.retellai.com/v1/call', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${RETELL_API_KEY}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(callConfig)
    });

    if (!response.ok) {
      const error = await response.json();
      throw new Error(`Call initiation failed: ${error.message} (status: ${response.status})`);
    }

    const call = await response.json();
    activeCallIds.add(call.call_id);
    return call;
  } catch (error) {
    console.error('Outbound call error:', error);
    throw error;
  }
}

// Webhook signature validation (prevents replay attacks)
function validateWebhookSignature(payload, signature) {
  const expectedSignature = crypto
    .createHmac('sha256', RETELL_WEBHOOK_SECRET)
    .update(JSON.stringify(payload))
    .digest('hex');

  if (signature !== expectedSignature) {
    throw new Error('Invalid webhook signature');
  }
}

// Webhook handler for call events
app.post('/webhook/retell', (req, res) => {
  const signature = req.headers['x-retell-signature'];
  const payload = req.body;

  try {
    validateWebhookSignature(payload, signature);

    const { event, call_id } = payload;

    switch (event) {
      case 'call_started':
        console.log(`Call ${call_id} started`);
        break;
      case 'call_ended':
        console.log(`Call ${call_id} ended`);
        activeCallIds.delete(call_id);
        break;
      case 'call_analyzed':
        console.log(`Call ${call_id} analysis:`, payload.analysis);
        break;
      default:
        console.log(`Unknown event: ${event}`);
    }

    res.status(200).json({ status: 'received' });
  } catch (error) {
    console.error('Webhook validation failed:', error);
    res.status(401).json({ error: 'Unauthorized' });
  }
});

// Main route to initiate outbound call
app.post('/initiate-call', async (req, res) => {
  if (isProcessing) {
    return res.status(429).json({ error: 'Call already in progress' });
  }

  isProcessing = true;

  try {
    const { to_number, from_number } = req.body;

    // Create assistant (cache this in production)
    const assistantId = await createAssistant();

    // Make outbound call
    const call = await makeOutboundCall(assistantId, to_number, from_number);

    res.status(200).json({
      status: 'initiated',
      call_id: call.call_id,
      assistant_id: assistantId
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  } finally {
    isProcessing = false;
  }
});

// Health check
app.get('/health', (req, res) => {
  res.status(200).json({ 
    status: 'healthy',
    active_calls: activeCallIds.size
  });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

Run Instructions

Environment Setup: Create .env file with your credentials:

bash
RETELL_API_KEY=your_retell_api_key_here
TWILIO_ACCOUNT_SID=your_twilio_sid_here
TWILIO_AUTH_TOKEN=your_twilio_auth_token_here
RETELL_WEBHOOK_SECRET=your_webhook_secret_here
PORT=3000

Install Dependencies:

bash
npm install express dotenv

Start Server:

bash
node server.js

Test Outbound Call:

bash
curl -X POST http://localhost:3000/initiate-call \
  -H "Content-Type: application/json" \
  -d '{
    "to_number": "+14155551234",

## FAQ

### Technical Questions

**What authentication method does the /v1/call endpoint require?**

The /v1/call endpoint uses Bearer token authentication via the `Authorization` header. You pass your RETELL_API_KEY (obtained from the Retell dashboard) as `Authorization: Bearer ${RETELL_API_KEY}`. This is a standard OAuth 2.0 Bearer token pattern. The API validates the token on every request—if it's missing, expired, or invalid, you'll receive a 401 Unauthorized response. Store your RETELL_API_KEY in environment variables, never hardcode it.

**How do I format phone numbers for outbound calls?**

Phone numbers must be in E.164 format: a `+` prefix followed by country code and subscriber number (e.g., `+14155552671`). The validateE164 function uses the regex pattern `/^\+[1-9]\d{1,14}$/` to enforce this. Twilio requires E.164 formatting for the `from_number` and `to_number` fields in your call payload. Non-compliant numbers will fail silently or return a 400 Bad Request error.

**What's the difference between assistantId and assistant object in the call payload?**

You can reference an existing assistant by `assistantId` (a UUID string) or define an inline `assistant` object with full configuration (model, voice, transcriber). Using `assistantId` is faster for repeated calls; inline `assistant` objects are useful for dynamic configuration per call. You cannot use both simultaneously—the API will reject the request if both are present.

### Performance

**Why is my outbound call latency high?**

Latency spikes typically occur during: (1) assistant initialization (100-300ms if not cached), (2) Twilio SIP trunk provisioning (500-1500ms), (3) STT model loading on first request. Mitigate by pre-creating assistants and reusing `assistantId` instead of inline configs. Monitor webhook response timesif your server takes >5s to acknowledge the `call_started` event, Retell may timeout and retry, doubling latency.

**How many concurrent outbound calls can I make?**

This depends on your Retell plan tier and Twilio account limits. Track active calls using `activeCallIds` (a Set or Map) to prevent resource exhaustion. Each concurrent call consumes STT, TTS, and LLM tokens. Implement backpressure: if `activeCallIds.size >= MAX_CONCURRENT`, queue the request and process when a call ends.

### Platform Comparison

**Should I use Retell AI or Twilio's native voice APIs for outbound calls?**

Retell AI handles conversational logic (STT, LLM, TTS) natively; Twilio handles carrier routing and SIP trunking. Use Retell's /v1/call endpoint when you need intelligent, context-aware conversations. Use Twilio's Programmable Voice API directly if you need low-level call control (DTMF, call transfers, IVR trees) without AI. Most hybrid setups use Retell for agent override logic and Twilio for fallback PSTN routing.

**Can I override the assistant mid-call?**

Yes. Use the Agent override feature by sending a webhook event or API call to update the `assistant` configuration. This is useful for escalating to a human agent or switching conversation modes. However, the override must happen before the call enters the active conversation phase—mid-stream overrides may cause audio glitches or dropped context.

## Resources

**Retell AI Official Documentation**
- [API Reference](https://docs.retellai.com/api-references/overview) – Complete endpoint specifications, authentication methods, and request/response schemas for /v1/call, /v1/assistant, and webhook event structures.
- [Outbound Call Guide](https://docs.retellai.com/features/outbound-calls) – E.164 phone number formatting, call registration requirements, and agent override configurations.

**Twilio Integration**
- [Twilio Voice API Docs](https://www.twilio.com/docs/voice/api) – SIP URI handling, call control, and TWILIO_ACCOUNT_SID/TWILIO_AUTH_TOKEN credential setup.

**GitHub & Code Examples**
- [Retell SDK Repository](https://github.com/RetellAI/retell-sdk-js) – Production-grade Node.js examples for RETELL_API_KEY authentication and call registration workflows.

Written by

Misal Azeem
Misal Azeem

Voice AI Engineer & Creator

Building production voice AI systems and sharing what I learn. Focused on VAPI, LLM integrations, and real-time communication. Documenting the challenges most tutorials skip.

VAPIVoice AILLM IntegrationWebRTC

Found this helpful?

Share it with other developers building voice AI.

Advertisement