name: voice-assistant description: "AI-powered voice assistant combining ElevenLabs Conversational AI with Twilio telephony. Build two-way voice agents for any scenario: birthday bots, sales assistants, customer support, appointment booking, surveys, and more. Supports agent versioning, A/B testing, knowledge bases, custom tools, and 30+ languages." homepage: https://elevenlabs.io/docs/agents-platform/overview tags: ["voice", "calling", "elevenlabs", "twilio", "conversation", "sales", "support", "ai-agent", "convai", "hackathon"] metadata: {"openclaw":{"emoji":"๐๏ธ","requires":{"env":["ELEVENLABS_API_KEY","TWILIO_ACCOUNT_SID","TWILIO_AUTH_TOKEN","TWILIO_PHONE_NUMBER"],"bins":["curl","jq"]},"primaryEnv":"ELEVENLABS_API_KEY"}}
Voice Assistant
Build production-ready AI voice agents for any calling scenario. ElevenLabs handles the conversation intelligence, Twilio handles the phone infrastructure.
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ElevenLabs ConvAI โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โ โ ASR โโโ LLM โโโ TTS โโโ Turn-Taking โ โ
โ โ (Speech โ โ(Reason) โ โ (Voice) โ โ Model โ โ
โ โ to Text)โ โ โ โ โ โ โ โ
โ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโโโโโ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ WebSocket/WebRTC
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Twilio Telephony โ
โ Phone calls, PSTN connectivity, call routing โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Setup
Environment Variables
# ElevenLabs
export ELEVENLABS_API_KEY="sk_..."
export ELEVENLABS_AGENT_ID="agent_..." # After creating agent
# Twilio
export TWILIO_ACCOUNT_SID="AC..."
export TWILIO_AUTH_TOKEN="..."
export TWILIO_PHONE_NUMBER="+1234567890"
Package Installation
# React SDK (recommended for web)
npm install @elevenlabs/react@0.12.3 zod
# JavaScript SDK (browser + server)
npm install @elevenlabs/client@0.12.2
# Server-only SDK (Node.js - full API access)
npm install @elevenlabs/elevenlabs-js@2.30.0
# CLI for "Agents as Code"
npm install -g @elevenlabs/agents-cli@0.6.1
Quick Start: Create Your First Agent
Option 1: API (Programmatic)
# Create agent
curl -X POST "https://api.elevenlabs.io/v1/convai/agents" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Birthday Bot",
"conversation_config": {
"agent": {
"prompt": {
"prompt": "You are calling to wish someone a happy birthday. Be warm, enthusiastic, and genuine. Ask about their plans. Keep it under 3 minutes.",
"llm": "gpt-4o",
"temperature": 0.7
},
"first_message": "Happy birthday! I wanted to call and celebrate with you!",
"language": "en"
},
"tts": {
"model_id": "eleven_turbo_v2_5",
"voice_id": "IKne3meq5aSn9XLyUdCD"
},
"turn": {
"mode": "normal"
}
}
}'
Option 2: CLI ("Agents as Code")
elevenlabs auth login
elevenlabs agents init
elevenlabs agents add "Birthday Bot" --template customer-service
# Edit agent_configs/birthday-bot.json
elevenlabs agents push --env prod
Option 3: JavaScript SDK
import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js';
const client = new ElevenLabsClient({ apiKey: process.env.ELEVENLABS_API_KEY });
const agent = await client.conversationalAi.agents.create({
name: 'Birthday Bot',
conversationConfig: {
agent: {
prompt: {
prompt: 'You are calling to wish someone a happy birthday...',
llm: 'gpt-4o',
temperature: 0.7
},
firstMessage: 'Happy birthday!',
language: 'en'
},
tts: {
modelId: 'eleven_turbo_v2_5',
voiceId: 'IKne3meq5aSn9XLyUdCD'
}
}
});
console.log('Agent ID:', agent.agentId);
Making Outbound Calls
Full Two-Way Conversation (ConvAI + Twilio)
# Step 1: Get signed URL from ElevenLabs
SIGNED_URL=$(curl -s -X POST "https://api.elevenlabs.io/v1/convai/twilio/outbound" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "'$ELEVENLABS_AGENT_ID'",
"agent_phone_number": "'$TWILIO_PHONE_NUMBER'",
"to_number": "+15551234567"
}' | jq -r '.signed_url')
# Step 2: Initiate call via Twilio
curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json" \
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
--data-urlencode "To=+15551234567" \
--data-urlencode "From=$TWILIO_PHONE_NUMBER" \
--data-urlencode "Url=$SIGNED_URL"
One-Way AI Voice Message (TTS Only)
For announcements where you don't need conversation:
# Step 1: Generate audio with ElevenLabs
curl -X POST "https://api.elevenlabs.io/v1/text-to-speech/IKne3meq5aSn9XLyUdCD" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"text": "Happy birthday! I wanted to be one of the first to wish you an amazing day!",
"model_id": "eleven_turbo_v2_5",
"voice_settings": { "stability": 0.5, "similarity_boost": 0.75 }
}' \
--output /tmp/message.mp3
# Step 2: Upload to temporary hosting
UPLOAD=$(curl -s -X POST -F "file=@/tmp/message.mp3" "https://tmpfiles.org/api/v1/upload")
AUDIO_URL=$(echo $UPLOAD | jq -r '.data.url' | sed 's|tmpfiles.org/|tmpfiles.org/dl/|')
# Step 3: Make call with Twilio
curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json" \
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
--data-urlencode "To=+15551234567" \
--data-urlencode "From=$TWILIO_PHONE_NUMBER" \
--data-urlencode "Twiml=<Response><Play>$AUDIO_URL</Play></Response>"
Agent API Reference
Agents CRUD
# List all agents
curl -X GET "https://api.elevenlabs.io/v1/convai/agents" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Get specific agent
curl -X GET "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Update agent
curl -X PATCH "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"conversation_config": {
"agent": {
"prompt": { "prompt": "Updated system prompt..." }
}
}
}'
# Delete agent
curl -X DELETE "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Archive agent (soft delete)
curl -X PATCH "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "archived": true }'
Conversations
# List conversations
curl -X GET "https://api.elevenlabs.io/v1/convai/conversations?agent_id=$AGENT_ID&limit=20" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Get conversation with transcript
curl -X GET "https://api.elevenlabs.io/v1/convai/conversations/$CONVERSATION_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Get conversation audio
curl -X GET "https://api.elevenlabs.io/v1/convai/conversations/$CONVERSATION_ID/audio" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
--output conversation.mp3
# Start conversation with dynamic variables
curl -X POST "https://api.elevenlabs.io/v1/convai/conversations" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "'$AGENT_ID'",
"dynamic_variables": {
"user_name": "John",
"birthday_age": "30",
"favorite_memory": "the trip to Hawaii last year"
}
}'
Agent Versioning & A/B Testing
ElevenLabs supports git-like version control for agents. Perfect for testing new prompts safely.
Enable Versioning
curl -X PATCH "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enable_versioning_if_not_enabled": true }'
Branch Management
# Create branch for experimentation
curl -X POST "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID/branches" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"parent_version_id": "agtvrsn_xxxx",
"name": "experiment-warmer-tone"
}'
# List branches
curl -X GET "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID/branches" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Delete branch
curl -X DELETE "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID/branches/$BRANCH_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
Traffic Deployment (A/B Testing)
# Deploy 90/10 traffic split
curl -X POST "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID/deployments" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"deployments": [
{ "branch_id": "agtbrch_main", "percentage": 90 },
{ "branch_id": "agtbrch_experiment", "percentage": 10 }
]
}'
# Get deployment status
curl -X GET "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID/deployments" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
Merge Successful Experiment
curl -X POST "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID/branches/merge" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source_branch_id": "agtbrch_experiment",
"target_branch_id": "agtbrch_main",
"archive_source_branch": true
}'
Testing Framework
9 API endpoints for automated agent testing.
# Create test
curl -X POST "https://api.elevenlabs.io/v1/convai/tests" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Birthday Flow Test",
"agent_id": "'$AGENT_ID'",
"scenario": "User receives birthday call",
"success_criteria": [
"Agent wishes happy birthday enthusiastically",
"Agent asks about their plans",
"Call ends warmly"
],
"user_messages": ["Thank you!", "I am having a party tonight", "Thanks for calling!"]
}'
# Execute test
curl -X POST "https://api.elevenlabs.io/v1/convai/tests/$TEST_ID/execute" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Get test results
curl -X GET "https://api.elevenlabs.io/v1/convai/test-results/$RESULT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Get detailed debug info
curl -X GET "https://api.elevenlabs.io/v1/convai/test-results/$RESULT_ID/debug" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# List test invocations
curl -X GET "https://api.elevenlabs.io/v1/convai/test-invocations?agent_id=$AGENT_ID&page_size=30" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Resubmit failed test
curl -X POST "https://api.elevenlabs.io/v1/convai/test-invocations/$INVOCATION_ID/resubmit" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
Knowledge Base (RAG)
Give your agent access to documents without loading everything into context.
# Upload document
curl -X POST "https://api.elevenlabs.io/v1/convai/knowledge-base" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-F "file=@product_catalog.pdf" \
-F "name=Product Catalog"
# Compute RAG index (required before use)
curl -X POST "https://api.elevenlabs.io/v1/convai/knowledge-base/$DOC_ID/compute-rag-index" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "embedding_model": "e5_mistral_7b" }'
# Check index status
curl -X GET "https://api.elevenlabs.io/v1/convai/knowledge-base/$DOC_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY"
# Attach to agent
curl -X PATCH "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"conversation_config": {
"agent": {
"prompt": {
"knowledge_base": ["'$DOC_ID'"]
}
}
}
}'
Tools Configuration
Server Tools (Webhooks)
# Create webhook tool
curl -X POST "https://api.elevenlabs.io/v1/convai/tools" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "schedule_callback",
"description": "Schedule a callback at a specific time",
"type": "webhook",
"webhook_config": {
"url": "https://your-api.com/schedule",
"method": "POST",
"headers": {
"Authorization": "Bearer {{secret__api_key}}"
}
},
"parameters": {
"type": "object",
"properties": {
"phone": { "type": "string", "description": "Phone number to call back" },
"time": { "type": "string", "description": "ISO timestamp for callback" }
},
"required": ["phone", "time"]
}
}'
# Attach tool to agent
curl -X PATCH "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"conversation_config": {
"agent": {
"prompt": {
"tool_ids": ["tool_abc123"],
"built_in_tools": ["end_call"]
}
}
}
}'
Client Tools (Browser-Side)
import { useConversation } from '@elevenlabs/react';
const { startConversation } = useConversation({
agentId: 'your-agent-id',
clientTools: {
showConfetti: {
description: 'Display birthday confetti animation on screen',
parameters: z.object({
color: z.string().describe('Confetti color'),
duration: z.number().describe('Duration in seconds')
}),
handler: async ({ color, duration }) => {
triggerConfetti({ color, duration });
return { success: true };
}
},
playBirthdaySong: {
description: 'Play happy birthday song',
parameters: z.object({}),
handler: async () => {
audioPlayer.play('/birthday-song.mp3');
return { playing: true };
}
}
}
});
System Tools
Built-in tools that don't require external APIs:
end_call- Gracefully end the conversationtransfer_to_number- Transfer to another phone numbertransfer_agent- Hand off to another agentdetect_language- Identify caller's languagedtmf_playpad- Play DTMF tonesvoicemail_detection- Detect if reached voicemail
Dynamic Variables
Personalize conversations with runtime data.
System Variables (Auto-Populated)
| Variable | Description |
|---|---|
{{system__agent_id}} |
Current agent ID |
{{system__conversation_id}} |
Current conversation ID |
{{system__caller_id}} |
Caller's phone number |
{{system__called_number}} |
Your Twilio number |
{{system__call_duration_secs}} |
Call duration |
{{system__time_utc}} |
Current UTC time |
{{system__call_sid}} |
Twilio call SID |
Custom Variables
# Pass at conversation start
curl -X POST "https://api.elevenlabs.io/v1/convai/conversations" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"agent_id": "'$AGENT_ID'",
"dynamic_variables": {
"recipient_name": "Sarah",
"birthday_age": "30",
"sender_name": "Mom",
"special_message": "I am so proud of everything you have accomplished"
}
}'
Use in prompt:
You are calling {{recipient_name}} on behalf of {{sender_name}} to wish them a happy {{birthday_age}}th birthday.
Include this special message: "{{special_message}}"
Secret Variables
For sensitive data (API keys) - only used in headers, never sent to LLM:
{
"headers": {
"Authorization": "Bearer {{secret__api_key}}"
}
}
Webhooks (Post-Call Analytics)
Receive call data after each conversation.
Configure Webhook
curl -X PATCH "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"webhook_config": {
"url": "https://your-api.com/elevenlabs-webhook",
"secret": "your-hmac-secret"
}
}'
Verify Webhook (HMAC)
import crypto from 'crypto';
function verifyWebhook(payload: string, signature: string, secret: string): boolean {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return signature === expected;
}
// In your handler:
app.post('/elevenlabs-webhook', (req, res) => {
const signature = req.headers['elevenlabs-signature']; // Note: NO "X-" prefix!
if (!verifyWebhook(JSON.stringify(req.body), signature, WEBHOOK_SECRET)) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
res.status(200).send('OK'); // MUST return 200 or webhook gets disabled after 10 failures
});
Webhook Payload Structure
{
"conversation_id": "conv_xxx",
"agent_id": "agent_xxx",
"status": "completed",
"transcript": [
{ "role": "agent", "message": "Happy birthday!", "timestamp": 0.0 },
{ "role": "user", "message": "Thank you so much!", "timestamp": 2.5 }
],
"metadata": {
"call_duration_secs": 45,
"charging": {
"llm_price": 0.0036,
"llm_charge": 18,
"call_charge": 60
}
},
"analysis": {
"call_successful": "success",
"transcript_summary": "Agent wished user happy birthday, user expressed gratitude",
"data_collection_results": { ... },
"evaluation_criteria_results": { ... }
}
}
Voices
Pre-Built Voices
| Voice ID | Name | Accent | Best For |
|---|---|---|---|
IKne3meq5aSn9XLyUdCD |
Charlie | Australian | Friendly, casual |
JBFqnCBsd6RMkjVDRZzb |
George | British | Professional |
Xb7hH8MSUJpSbSDYk0k2 |
Alice | British | Warm, friendly |
EXAVITQu4vr4xnSDxMaL |
Sarah | American | Clear, neutral |
CwhRBWXzGAHq8TQ4Fs17 |
Roger | American | Authoritative |
Clone Your Voice
# Upload voice sample
curl -X POST "https://api.elevenlabs.io/v1/voices/add" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-F "name=My Voice" \
-F "files=@voice_sample.mp3" \
-F "description=My cloned voice for calling"
Best practices for voice cloning:
- 1-2 minutes of clear audio
- No background noise, music, or pops
- Consistent microphone distance
- Natural speaking pace
Voice Settings
{
"tts": {
"model_id": "eleven_turbo_v2_5",
"voice_id": "IKne3meq5aSn9XLyUdCD",
"voice_settings": {
"stability": 0.5,
"similarity_boost": 0.75,
"speed": 1.0
}
}
}
| Setting | Range | Effect |
|---|---|---|
stability |
0-1 | Higher = more consistent, lower = more expressive |
similarity_boost |
0-1 | Higher = closer to original voice |
speed |
0.7-1.2 | Speaking rate (use 0.9-1.1 for natural sound) |
Pronunciation Dictionary
curl -X PATCH "https://api.elevenlabs.io/v1/convai/agents/$AGENT_ID" \
-H "xi-api-key: $ELEVENLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"conversation_config": {
"tts": {
"pronunciation_dictionary": [
{ "word": "GIF", "pronunciation": "jif", "format": "cmu" },
{ "word": "SQL", "substitution": "sequel" }
]
}
}
}'
Scenario Templates
Birthday Wishes
{
"name": "Birthday Bot",
"conversation_config": {
"agent": {
"prompt": {
"prompt": "PERSONALITY:\nYou are a warm, enthusiastic friend calling to wish someone a happy birthday.\n\nBEHAVIOR:\n- Open with genuine excitement\n- Ask about their birthday plans\n- Share something you appreciate about them (use {{special_message}} if provided)\n- Keep energy positive throughout\n- End warmly when conversation naturally concludes\n\nGUARDRAILS:\n- Keep under 3 minutes unless they want to chat longer\n- If they seem busy, offer warm wishes and let them go\n- Never be pushy or overstay welcome",
"llm": "gpt-4o",
"temperature": 0.8
},
"first_message": "Happy birthday! I just had to call and celebrate with you!",
"language": "en"
},
"tts": {
"model_id": "eleven_turbo_v2_5",
"voice_id": "IKne3meq5aSn9XLyUdCD"
},
"turn": { "mode": "normal" }
}
}
Sales Assistant
{
"name": "Sales Assistant",
"conversation_config": {
"agent": {
"prompt": {
"prompt": "PERSONALITY:\nYou are Alex, a consultative sales rep. Your goal is to understand needs, NOT to push products.\n\nBEHAVIOR:\n- Ask permission: 'Do you have 2 minutes?'\n- Focus on THEIR challenges first\n- Only mention solutions that genuinely fit stated needs\n- Respect 'no' immediately and gracefully\n- Offer to send info via email if they prefer\n\nQUALIFICATION (weave naturally):\n- Budget: 'Are you funded for this initiative?'\n- Authority: 'Who else would be involved?'\n- Need: 'What problem are you solving?'\n- Timeline: 'When do you need this by?'\n\nTOOLS:\n- schedule_demo: Book a product demo\n- send_info: Email product information\n\nGUARDRAILS:\n- Never be pushy or desperate\n- Max 2 minutes for cold calls\n- Be honest if not a good fit",
"llm": "gpt-4o",
"temperature": 0.7,
"tool_ids": ["tool_schedule_demo", "tool_send_info"],
"built_in_tools": ["end_call"]
},
"first_message": "Hi, this is Alex from {{company_name}}. Is this a good time for a quick chat?",
"language": "en"
},
"turn": { "mode": "eager" }
}
}
Customer Support
{
"name": "Support Agent",
"conversation_config": {
"agent": {
"prompt": {
"prompt": "PERSONALITY:\nYou are a patient, empathetic support agent. Your goal is to solve problems while making customers feel heard.\n\nBEHAVIOR:\n- Acknowledge their frustration with empathy\n- Ask clarifying questions\n- Explain solutions clearly\n- Escalate to human if you cannot resolve\n- Follow up on whether solution worked\n\nTOOLS:\n- lookup_order: Check order status and details\n- process_refund: Issue refund (requires confirmation)\n- create_ticket: Escalate to human support\n\nGUARDRAILS:\n- Never be defensive\n- Never rush them off\n- Always provide clear next steps",
"llm": "gpt-4o",
"temperature": 0.5,
"tool_ids": ["tool_lookup_order", "tool_process_refund", "tool_create_ticket"],
"built_in_tools": ["end_call", "transfer_to_number"],
"knowledge_base": ["doc_faq", "doc_policies"]
},
"first_message": "Hi! Thanks for calling support. How can I help you today?",
"language": "en"
},
"turn": { "mode": "patient" }
}
}
Appointment Booking
{
"name": "Appointment Scheduler",
"conversation_config": {
"agent": {
"prompt": {
"prompt": "PERSONALITY:\nYou are a friendly, efficient appointment scheduler.\n\nBEHAVIOR:\n- State purpose clearly\n- Offer 2-3 specific time slots\n- If none work, ask for their preference\n- Confirm all details: time, location/link, what to prepare\n- Send calendar invite via tool\n\nTOOLS:\n- check_availability: Get available time slots\n- book_appointment: Create the appointment\n- send_confirmation: Email confirmation with details\n\nGUARDRAILS:\n- Always confirm before booking\n- Provide clear next steps\n- Keep under 5 minutes",
"llm": "gpt-4o",
"temperature": 0.6,
"tool_ids": ["tool_check_availability", "tool_book_appointment", "tool_send_confirmation"],
"built_in_tools": ["end_call"]
},
"first_message": "Hi {{customer_name}}! I'm calling to help schedule your {{appointment_type}}.",
"language": "en"
},
"turn": { "mode": "normal" }
}
}
Survey/Feedback
{
"name": "Survey Caller",
"conversation_config": {
"agent": {
"prompt": {
"prompt": "PERSONALITY:\nYou are conducting a brief feedback survey. Be respectful of their time.\n\nBEHAVIOR:\n- Ask permission: 'Do you have 2 minutes for quick feedback?'\n- Keep questions simple and direct\n- Listen without defending\n- Thank them sincerely\n- Offer to escalate any issues mentioned\n\nQUESTIONS:\n1. 'On a scale of 1-10, how satisfied were you with {{service_type}}?'\n2. 'What is one thing we could improve?'\n3. 'Would you recommend us to a friend?'\n\nGUARDRAILS:\n- Never exceed 2 minutes\n- Never argue with negative feedback\n- Accept all feedback gracefully",
"llm": "gpt-4o",
"temperature": 0.5,
"built_in_tools": ["end_call"]
},
"first_message": "Hi! I'm calling from {{company_name}}. Do you have 2 minutes for quick feedback on your recent {{service_type}}?",
"language": "en"
},
"turn": { "mode": "eager" }
}
}
Turn-Taking Modes
| Mode | Behavior | Best For |
|---|---|---|
eager |
Responds quickly, minimal pauses | Fast support, quick transactions |
normal |
Balanced timing (default) | General conversations |
patient |
Waits longer for user to finish | Complex topics, emotional calls |
Multi-Language Support
32+ languages with automatic detection.
{
"conversation_config": {
"agent": {
"language": "en",
"language_presets": [
{ "language": "en", "voice_id": "EXAVITQu4vr4xnSDxMaL", "first_message": "Hi! How can I help?" },
{ "language": "es", "voice_id": "spanish_voice_id", "first_message": "Hola! Como puedo ayudarte?" },
{ "language": "fr", "voice_id": "french_voice_id", "first_message": "Bonjour! Comment puis-je vous aider?" }
]
}
}
}
React Integration
'use client';
import { useConversation } from '@elevenlabs/react';
import { useCallback, useState } from 'react';
export function VoiceAssistant() {
const [isActive, setIsActive] = useState(false);
const conversation = useConversation({
agentId: process.env.NEXT_PUBLIC_AGENT_ID,
onConnect: () => setIsActive(true),
onDisconnect: () => setIsActive(false),
onEvent: (event) => {
if (event.type === 'transcript') {
console.log('User said:', event.text);
} else if (event.type === 'agent_response') {
console.log('Agent said:', event.text);
}
},
clientTools: {
showConfetti: {
description: 'Show birthday confetti',
parameters: z.object({}),
handler: async () => {
confetti({ particleCount: 100, spread: 70 });
return { success: true };
}
}
}
});
const startCall = useCallback(async () => {
await navigator.mediaDevices.getUserMedia({ audio: true });
await conversation.startSession({
dynamicVariables: {
recipient_name: 'Sarah',
birthday_age: '30'
}
});
}, [conversation]);
return (
<div>
<button onClick={isActive ? conversation.endSession : startCall}>
{isActive ? 'End Call' : 'Start Call'}
</button>
<p>Status: {conversation.status}</p>
<p>{conversation.isSpeaking ? 'Agent speaking...' : 'Listening...'}</p>
</div>
);
}
Twilio Call Options
| Option | Description |
|---|---|
--timeout 30 |
Ring for 30 seconds before giving up |
--record |
Record the call |
--machine-detection Enable |
Detect answering machines |
--send-digits "W1234#" |
Dial extension after connecting |
# With options
curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json" \
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
--data-urlencode "To=+15551234567" \
--data-urlencode "From=$TWILIO_PHONE_NUMBER" \
--data-urlencode "Url=$SIGNED_URL" \
--data-urlencode "Timeout=30" \
--data-urlencode "Record=true" \
--data-urlencode "MachineDetection=Enable"
Check Call Status
# List recent calls
curl -X GET "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json?PageSize=10" \
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN"
# Get specific call
curl -X GET "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls/$CALL_SID.json" \
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN"
Cost Breakdown
| Component | Cost | Notes |
|---|---|---|
| Twilio voice | $0.013/min | Outbound US calls |
| ElevenLabs TTS | ~$0.10/min audio | Turbo v2.5 |
| ElevenLabs ConvAI | ~$0.11-0.13/min | Two-way conversation |
| LLM (GPT-4o) | ~$0.003/call | Varies by conversation length |
Budget optimization:
- Use TTS-only for simple announcements
- Use ConvAI for conversations that need interactivity
- Cache common responses
- Enable LLM caching:
{ "caching": { "enabled": true, "ttl_seconds": 3600 } }
Common Errors & Solutions
| Error | Cause | Solution |
|---|---|---|
| Missing dynamic variables | Variables in prompt not provided | Pass all {{var}} in dynamic_variables |
| Tool name mismatch | Tool names are case-sensitive | Match exact case in tool_ids |
| Webhook disabled | Not returning 200 or 10+ failures | Always return 200, verify HMAC |
| 401 in production | Allowlist or API key issue | Check allowlist settings, verify key |
| Host not supported | Localhost allowlist bug | Use 127.0.0.1:3000 instead of localhost |
| WebSocket 1002 error | Network instability | Use WebRTC instead, add reconnection logic |
| RAG not working | Index not ready | Check index.status === 'ready' first |
| Wrong voice language | English voice for non-English | Use language-matched voices |
Best Practices
Do's
- Test on yourself first
- Use appropriate turn-taking mode
- Provide rich dynamic variables
- Handle errors gracefully
- Monitor webhook analytics
Don'ts
- Don't hide it's AI if asked directly
- Don't be pushy or aggressive
- Don't call at unreasonable hours
- Don't repeatedly call same person
- Don't use for deception