VAPI를 사용하여 고객 지원을 위한 AI Voice Agent 배포 방법

발행: (2025년 12월 4일 오후 05:45 GMT+9)
6 min read
원문: Dev.to

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

  1. VAPI 대시보드에서 어시스턴트의 전화 설정을 찾습니다.
  2. Twilio Console → Phone NumbersBuy NumberConfigure Webhook 로 이동합니다.
  3. 인바운드 웹훅 URL을 VAPI 어시스턴트의 전화 엔드포인트(대시보드에 제공)로 설정합니다.

아웃바운드 호출은 지원 티켓이 생성되거나 에스컬레이션될 때 프로그래밍 방식으로 트리거합니다.

Monitoring Metrics (first 100 calls)

MetricTarget
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 serverUrl in 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.

Back to Blog

관련 글

더 보기 »