Wixzel Voice로 AI 리드 선별 캠페인 실행하기
이 가이드를 마치면 리드 목록을 업로드하고, 직접 작성한 에이전트가 직접 보유한 SIP 트렁크로 각 리드에게 전화를 걸어 선별 질문을 하게 하고, 모든 통화의 상태와 대화 기록을 읽어 오게 됩니다. 구현은 Node.js와 wixzel-voice SDK로 약 30분이 걸리고, 여기에 통화 시간이 더해집니다. 짧은 스크립트 통화는 가장 저렴한 작업입니다. classic 엔진에서 연결된 1분당 $0.0867이므로, 2분 통화 1,000건의 엔진 사용 요금은 약 $173이며, 선불 잔액에서 초 단위로 청구되고 통신사 요금이 별도로 붙습니다. 아무도 받지 않은 통화는 요금이 없습니다.
작성 Aqeel Shamsudheen · 게시 · 업데이트
- 소요 시간
- 약 30분, 여기에 통화 시간 추가
- 엔진
- classic
- 엔진 사용 시간
- 연결된 1분당 $0.0867
시작하기 전에 무엇이 필요한가요?
- SIP 트렁크와 그 트렁크에 등록된 발신 번호. Twilio 가이드와 SIP 트렁크 문서에서 둘 다 다룹니다.
- 크레딧이 있는 Wixzel Voice API 키, 그리고
npm install wixzel-voice를 실행한 Node.js 20 이상. - 법이 요구하는 경우, 목록의 모든 리드에게 전화를 걸어도 된다는 동의. 아래 규정 준수 단계에서 허용 사용 정책의 요구 사항을 요약합니다.
Wixzel Voice에서 단계별로 어떻게 만드나요?
01발신 에이전트 작성하기
에이전트가 곧 스크립트입니다. 병합 필드로 통화마다 개인화합니다.
agent.mjsimport { WixzelVoice } from 'wixzel-voice'; const client = new WixzelVoice({ apiKey: process.env.WIXZEL_API_KEY }); const agent = await client.agents.create({ name: 'Demo follow-up', outbound_phone_number_id: process.env.NUMBER_ID, system_prompt: [ 'You are calling {{name}} from {{company}}, who asked for a demo of Acme Scheduling.', 'Ask three questions, one at a time: how many staff take bookings,', 'what they use for bookings today, and when they would want to switch.', 'Keep replies short. If they are not interested, thank them and end the call.', 'If they ask not to be called again, confirm that you have noted it and end the call.', 'Finish by thanking them and saying a person will follow up by email.', ].join('\n'), opening_message: 'Hi {{name}}, this is an automated assistant calling for Acme about the demo you requested. Is now a good time?', voice: { stt: { model: 'deepgram/nova-3', language: 'en-US' }, llm: { model: 'openrouter/gpt-4o-mini' }, tts: { model: 'elevenlabs/eleven_flash_v2_5' }, }, }); console.log('AGENT_ID', agent.id);{{name}}은 리드에서 채워지고, 리드의fields에 있는 모든 키도 병합 필드가 되므로{{company}}는 아래 업로드에서 가져옵니다. 첫 인사에서 자동 발신임을 밝히는데, 법이 이 고지를 요구할 때 들어갈 곳이 바로 여기입니다. 캠페인에는outbound_phone_number_id가 필수이며, 모든 통화가 표시하는 발신자 번호입니다.02리드 업로드하기
나머지 구현은
campaign.mjs스크립트 하나입니다. 업로드부터 시작합니다.campaign.mjs, part 1import { WixzelVoice } from 'wixzel-voice'; const client = new WixzelVoice({ apiKey: process.env.WIXZEL_API_KEY }); // From your CRM or a CSV: only people you have consent to call. const rows = [ { name: 'Priya Nair', phone_number: '+14155550111', fields: { company: 'Northside Physio' } }, { name: 'Sam Carter', phone_number: '+14155550112', fields: { company: 'Carter Auto' } }, ]; const upload = await client.leads.bulkCreate({ leads: rows }); console.log('created', upload.created_count, 'failed', upload.failed_count); for (const e of upload.errors) console.log('row', e.index, e.phone_number, e.error); const leadIds = upload.data.map((lead) => lead.id);한 번의 요청으로 최대 1,000개의 리드를 올릴 수 있습니다. 행은 하나씩 검증되므로 목록 전체가 들어갔다고 가정하지 말고
failed_count와errors를 확인하십시오. 일괄 업로드는 웹훅을 발생시키지 않습니다.03동시 통화 한도에 맞춰 캠페인 실행하기
캠페인은 에이전트 하나와 리드 목록입니다. 캠페인을 만들기만 해서는 아무에게도 전화를 걸지 않습니다.
start가 발신을 시작하고, 각 통화가 연결될 때 크레딧을 사용합니다.현재 캠페인은 통화를 대기열에 넣지 않고 빠르게 연달아 겁니다. 각 통화는 계정의 동시 통화 한도(기본 5건)에 포함되며, 한도를 넘은 리드는 차례를 기다리지 않고 실패한 통화로 기록됩니다. 따라서 목록을 한도 이하 크기의 배치로 나눠 실행하고, 이전 배치가 끝나면 다음 배치를 시작합니다.
campaign.mjs, part 2const LIMIT = 5; // your account's concurrent-call limit, minus any calls you expect meanwhile const DONE = new Set(['completed', 'failed', 'busy', 'no-answer', 'canceled']); const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function finishedCalls(campaignId) { let finished = 0; for await (const call of await client.calls.list({ campaign_id: campaignId, limit: 100 })) { if (DONE.has(call.status)) finished++; } return finished; } const campaignIds = []; for (let i = 0; i < leadIds.length; i += LIMIT) { const batch = leadIds.slice(i, i + LIMIT); const campaign = await client.campaigns.create({ name: 'Demo follow-up ' + (i / LIMIT + 1), agent_id: process.env.AGENT_ID, lead_ids: batch, }); await client.campaigns.start(campaign.id); campaignIds.push(campaign.id); const giveUpAt = Date.now() + 30 * 60 * 1000; while ((await finishedCalls(campaign.id)) < batch.length && Date.now() < giveUpAt) { await sleep(15000); } }start는status: "running"인 캠페인을 반환합니다. 배치를 일찍 멈추려면client.campaigns.pause(campaign.id)를 호출합니다. 캠페인을 멈추고 진행 중인 통화를 끊으며, 이후 캠페인은status: "stopped"가 됩니다. 멈춘 캠페인을 다시 시작하면 이미 통화한 리드를 포함해 모든 리드에게 다시 전화를 걸므로, 이어서 진행하려면 아직 연락하지 못한 리드로 새 캠페인을 만드십시오. 생성 시scheduled_at을 지정하면start대신 정해진 시각에 캠페인이 시작됩니다.04모든 통화 확인하기
각 통화는 상태, 비용, 대화 기록이 담긴 기록이며
lead_id로 리드와 연결됩니다.campaign.mjs, part 3for (const campaignId of campaignIds) { for await (const call of await client.calls.list({ campaign_id: campaignId, limit: 100 })) { const detail = await client.calls.retrieve(call.id); const answers = detail.transcript .filter((line) => line.role === 'user') .map((line) => line.content) .join(' / '); console.log(call.lead_id, call.status, call.duration_seconds, call.failure_code, answers); } }status는 통화가 연결되었는지,failure_code는 연결되지 않은 이유(17 통화 중, 19 응답 없음, 21 통신사 거부),cost_micros는 비용을 알려 줍니다. 동시 통화 한도나 크레딧 부족으로 발신 전에 거부된 리드는failure_code없이failed로 표시됩니다.선별 판단은 사용자의 몫입니다. Wixzel Voice는 대화를 기록하고, 대화 기록에 세 가지 질문에 대한 답이 담깁니다. 자체 규칙이나 자체 모델로 점수를 매기고 결과를 CRM에 기록하십시오.
summary필드는 비어 있을 수 있으므로 의존하지 마십시오. 폴링 대신 통화가 끝날 때 바로 처리하려면callCompleted웹훅을 구독하십시오. 이 웹훅에는 리드 ID와 캠페인 ID, 통화 상태와 길이가 담깁니다.05발신 규정 지키기
허용 사용 정책은 아래 책임을 발신자, 즉 사용자에게 둡니다. 이 내용은 정책의 요약이며 법률 자문이 아닙니다.
법이 요구하는 경우 동의를 받은 사람에게만 전화하십시오. 미국에서는 일반적으로 TCPA에 따라 휴대폰 번호로 마케팅 전화를 걸려면 사전 서면 동의가 필요하고, 인도에서는 TRAI 고객 선호 규정, 영국과 EU에서는 PECR과 GDPR이 적용됩니다.
업로드할 때마다 국가 및 주 단위 수신 거부 등록부와 자체 수신 거부 목록을 확인하십시오. 다시 전화하지 말라는 요청을 받으면 신속하고 영구적으로 목록에서 제외하십시오. 에이전트는 통화를 정중하게 끝낼 수 있지만, 리드를 제거하는 것은 코드가 할 일입니다.
법이 요구하는 경우 위의 첫 인사처럼 통화 시작 시 자동 발신임을 밝히고, 통화 가능 시간 제한을 지키고, 법이 요구하는 기간 동안 동의 증빙을 보관하십시오.
Wixzel Voice에서 이 빌드의 비용은 얼마인가요?
classic 엔진에서 연결된 1분당 $0.0867로 2분 통화를 할 때의 엔진 사용 시간 비용: 통화당 약 $0.17. 전화 통신은 자체 트렁크에서 통신사 요금으로 청구되며 포함되지 않습니다.
| 월 통화 수 | 연결된 시간(분) | 월 엔진 비용 |
|---|---|---|
| 1,000 | 2,000 | $173 |
| 5,000 | 10,000 | $867 |
| 20,000 | 40,000 | $3,468 |
연결된 시간만 초 단위로 청구됩니다. 통화 중이거나, 응답이 없거나, 발신 전에 거부된 통화는 엔진이 시작되지 않으므로 요금이 없습니다. 회선 요금은 통신사가 자체 요금으로 마진 없이 별도 청구합니다.
선불이며 구독과 무료 등급이 없습니다. $5부터 크레딧을 충전하면 사용량만큼 차감됩니다. 내 사용량으로 추정해 보기.
이 빌드에는 Wixzel Voice의 어떤 한도가 적용되나요?
- 캠페인은 리드에게 빠르게 연달아 전화를 걸며, 동시 통화 한도(계정당 기본 5건)에 맞춰 대기하지 않습니다. 한도를 넘은 리드는 실패로 기록됩니다. 한도에 맞춰 배치로 나누거나 한도 상향을 요청하십시오.
- 멈춘 캠페인을 다시 시작하면 모든 리드에게 다시 전화를 겁니다. 아직 연락하지 못한 리드로 새 캠페인을 만들어 이어 가십시오.
- 캠페인 하나에는 최대 10,000개의 리드를 넣을 수 있고, 계정 하나에는 기본적으로 최대 500개의 캠페인을 둘 수 있으며, 요청하면 늘려 드립니다.
- Wixzel Voice는 통화와 대화 기록을 저장합니다. 누가 선별 기준을 통과했는지 판단하고 수신 거부 목록을 관리하는 것은 코드가 할 일입니다.
- Wixzel Voice의 동작 변경은 날짜 기반
Wixzel-Version헤더로 구분되어 배포됩니다.
자주 묻는 질문
Wixzel Voice에서 AI 발신 캠페인을 실행하려면 어떻게 해야 하나요?
POST /v1/leads/bulk로 리드를 업로드하고, 에이전트와 그 리드로 캠페인을 만든 뒤, POST /v1/campaigns/{id}/start를 호출하면 에이전트가 직접 보유한 SIP 트렁크로 각 리드에게 연달아 전화를 겁니다. 한도를 넘은 리드는 대기하지 않고 실패로 표시되므로, 캠페인 크기를 동시 통화 한도 이하로 유지하십시오. 모든 통화는 상태, 대화 기록, 비용이 담긴 기록이며 lead_id로 리드와 연결됩니다. 단일 통화는 POST /v1/calls를 사용합니다.발신 통화를 동시에 몇 건까지 진행할 수 있나요?
캠페인과 단일 통화 중 무엇을 써야 하나요?
POST /v1/calls를 호출하는 편이 더 세밀합니다. 각 통화에 lead_id와 멱등성 키를 넘기고, 다음 통화를 언제 시작할지 직접 정합니다.AI 발신 전화는 합법인가요?
더 읽을거리
Wixzel Voice 소개
Wixzel Voice는 자체 SIP 트렁크로 실제 전화를 걸고 받거나 웹·모바일 앱에서 사람들과 대화하는 AI 에이전트를 만들기 위한 음성 AI API입니다. API 키 하나와 선불 잔액 하나로 모든 음성 엔진을 사용하며, 실제 사용량에 따라 초 단위로 과금됩니다.
더 많은 가이드
Claude Code나 claude.ai의 Claude가 SIP 트렁크로 실제 전화를 걸고 대화 기록을 읽어 주는 상태. 전화를 걸기 전에는 항상 확인을 거칩니다.
Node.js로 만든 AI 접수원이 기존 Twilio 번호로 오는 전화를 받고 API에 예약을 잡는 상태.
Sarvam의 인도 언어 모델로 동작하며 SIP 트렁크로 전화를 받거나 거는 전화 에이전트 두 개(힌디어 하나, 말라얄람어 하나).
API 키는 서버에 두고, 전화번호 없이 사용자가 WebSocket으로 음성 에이전트와 대화하는 Flutter 앱.
cURL 요청 네 번으로 SIP 트렁크를 통해 실제 전화를 거는 Gemini Live 에이전트, 그리고 대화 기록과 비용이 담긴 통화 기록.
웹사이트 구석에서 방문자가 브라우저로 통화하는 말하는 마스코트. 지정한 도메인과 일일 한도 안에서만 동작하고, 모든 발신자가 목록에 남습니다.
실시간 통화 중 에이전트 음성에 맞춰 입을 움직이는 에이전트 마스코트가 있는 Flutter 화면. API 키는 서버에 둡니다.

