VAPI를 사용하여 고객 지원을 위한 AI Voice Agent 배포 방법
Source: Dev.to
TL;DR
대부분의 음성 에이전트는 고객이 문장 중간에 말을 끊거나 통화 볼륨이 급증할 때 중단됩니다. 이 가이드는 VAPI의 기본 음성 인프라와 Twilio의 통신 등급 전화 시스템을 사용해 두 상황을 모두 처리할 수 있는 프로덕션 수준 AI 음성 에이전트를 구축하는 방법을 보여줍니다. 500 ms 미만의 응답 시간, 정확한 바지인 처리, API 타임아웃 시 자동 페일오버를 기대하세요.
Stack
- VAPI – 음성 AI
- Twilio – 전화 라우팅
- Webhook server – 비즈니스 로직 통합
API Access & Authentication
- VAPI API 키 –
dashboard.vapi.ai에서 발급 - Twilio Account SID 및 Auth Token –
console.twilio.com에서 확인 - 음성 기능이 활성화된 Twilio 전화번호
- OpenAI API 키 – GPT‑4 모델 접근용
Development Environment
- Node.js 18+ or Python 3.9+
- ngrok (또는 유사한 터널링 도구) – 웹훅 테스트용
- Git – 버전 관리
Technical Requirements
- 웹훅 핸들러용 공개 HTTPS 엔드포인트 (프로덕션)
- SSL 인증서 (Let’s Encrypt 사용 가능)
- 서버 메모리 ≥ 512 MB (권장 1 GB)
- 안정적인 인터넷 연결 (실시간 오디오 전송을 위한 업로드 ≥ 10 Mbps)
Knowledge Assumptions
- REST API 통합 경험
- 웹훅 이벤트 처리 패턴
- 음성 프로토콜 기본 이해 (SIP, WebRTC)
- JSON 설정 관리
Cost Awareness
| 서비스 | 대략적인 비용 |
|---|---|
| VAPI (모델 + 음성 합성) | 분당 $0.05 – $0.10 |
| Twilio (음성 통화 분당) | 분당 $0.0085 |
| Twilio 전화번호 | 월 $1 |
Architecture Overview
flowchart LR
A[Customer Calls] --> B[Twilio Number]
B --> C[VAPI Assistant]
C --> D[Your Webhook Server]
D --> E[CRM/Database]
E --> D
D --> C
C --> B
B --> A
Twilio가 전화 라우팅을 담당하고, VAPI가 음성 AI를 담당합니다. 서버는 웹훅을 통해 두 시스템을 연결합니다. 이러한 역할을 분리하여 팬텀 오디오 문제를 방지하세요.
Assistant Configuration
const assistantConfig = {
name: "Support Agent",
model: {
provider: "openai",
model: "gpt-4",
temperature: 0.7,
systemPrompt: "You are a customer support agent. Extract: customer name, issue type, account number. If caller interrupts, acknowledge immediately and adjust."
},
voice: {
provider: "11labs",
voiceId: "21m00Tcm4TlvDq8ikWAM",
stability: 0.5,
similarityBoost: 0.75
},
transcriber: {
provider: "deepgram",
model: "nova-2",
language: "en",
endpointing: 255 // ms silence before considering speech ended
},
recordingEnabled: true,
serverUrl: process.env.WEBHOOK_URL,
serverUrlSecret: process.env.WEBHOOK_SECRET
};
Tip: endpointing = 255 ms는 공격적인 설정이며 빠른 지원 상호작용에 잘 맞습니다. 모바일 네트워크가 불안정할 경우 400 ms로 늘리세요.
Webhook Server (Node.js + Express)
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// Validate webhook signatures – production requirement
function validateSignature(req) {
const signature = req.headers['x-vapi-signature'];
const payload = JSON.stringify(req.body);
const hash = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return signature === hash;
}
app.post('/webhook/vapi', async (req, res) => {
if (!validateSignature(req)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const { message } = req.body;
try {
switch (message.type) {
case 'function-call':
// Example: fetch customer data from CRM
const customerData = await fetchCustomerData(message.functionCall.parameters.accountNumber);
res.json({ result: customerData });
break;
case 'end-of-call-report':
// Log call metrics
await logCallMetrics({
callId: message.call.id,
duration: message.call.endedAt - message.call.startedAt,
cost: message.call.cost,
transcript: message.transcript
});
res.sendStatus(200);
break;
case 'speech-update':
// Real‑time transcript for live‑agent handoff
if (message.status === 'in-progress') {
await updateLiveTranscript(message.call.id, message.transcript);
}
res.sendStatus(200);
break;
default:
res.sendStatus(200);
}
} catch (error) {
console.error('Webhook error:', error);
res.status(500).json({ error: 'Processing failed' });
}
});
app.listen(3000);
Important: VAPI는 5 초 이내에 응답을 받기를 기대합니다. 외부 호출이 느릴 경우 즉시 res.sendStatus(202)를 반환하고 작업을 비동기로 처리한 뒤, 결과를 VAPI API를 통해 나중에 전송하세요.
Connecting Twilio to VAPI
- VAPI 대시보드에서 어시스턴트의 전화 설정을 찾습니다.
- Twilio Console → Phone Numbers → Buy Number → Configure Webhook 로 이동합니다.
- 인바운드 웹훅 URL을 VAPI 어시스턴트의 전화 엔드포인트(대시보드에 제공)로 설정합니다.
아웃바운드 호출은 지원 티켓이 생성되거나 에스컬레이션될 때 프로그래밍 방식으로 트리거합니다.
Monitoring Metrics (first 100 calls)
| Metric | Target |
|---|---|
| Interruption accuracy | > 95 % |
| False barge‑ins | 92 % |
| Transcription accuracy (noisy) | > 85 % |
중단 정확도가 90 % 이하로 떨어지면 endpointing을 300 ms로 늘리고, 음성 stability를 0.4로 낮춰 더 빠르게 차단하도록 조정하세요.
Audio Processing Pipeline
graph LR
A[Microphone] --> B[Audio Buffer]
B --> C[Voice Activity Detection]
C -->|Speech Detected| D[Speech-to-Text]
C -->|No Speech| E[Error: Silence]
D --> F[Intent Detection]
F --> G[Response Generation]
G --> H[Text-to-Speech]
H --> I[Speaker]
D -->|Error: Unrecognized Speech| J[Error Handling]
J --> F
F -->|Error: No Intent| K[Fallback Response]
K --> G
Local Testing with ngrok
# Start ngrok tunnel (run in terminal)
ngrok http 3000
Example curl test (Node.js snippet)
const crypto = require('crypto');
const fetch = require('node-fetch');
const testPayload = {
message: {
type: "function-call",
functionCall: {
name: "getCustomerData",
parameters: { customerId: "test-123" }
}
}
};
const hash = crypto
.createHmac('sha256', process.env.VAPI_SERVER_SECRET)
.update(JSON.stringify(testPayload))
.digest('hex');
fetch('https://your-ngrok-url.ngrok.io/webhook/vapi', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-vapi-signature': hash
},
body: JSON.stringify(testPayload)
})
.then(res => res.json())
.then(data => console.log('Webhook response:', data))
.catch(err => console.error('Webhook failed:', err));
Note: Free‑tier ngrok URLs expire after 2 hours. Update the
serverUrlin the VAPI dashboard after each restart to avoid 404 errors.
Signature Validation
The validateSignature function compares the HMAC‑SHA256 hash of the request payload against the x-vapi-signature header. Mismatched signatures result in a 401 response, preventing replay attacks and unauthorized triggering of expensive API calls.
Customer calls → Twilio → VAPI → Your webhook → CRM/Database → (loop) → VAPI → Twilio → Customer.