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リードをアップロードする
残りの構築は1つのスクリプト
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回のリクエストで最大1,000件のリードを登録できます。行は1件ずつ検証されるため、一覧全体が登録されたと考えず、
failed_countとerrorsを確認してください。一括アップロードではWebhookは送信されません。03同時通話数の上限に合わせてキャンペーンを実行する
キャンペーンは、1つのエージェントとリードの一覧で構成されます。作成しただけでは誰にも発信しません。
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は会話を記録し、文字起こしには3つの質問への回答が含まれます。独自のルールや独自のモデルで評価し、結果をCRMに書き込んでください。
summaryフィールドは空の場合があるので、それに依存しないでください。ポーリングではなく通話の終了時に処理したい場合は、callCompletedWebhookを購読します。リードとキャンペーンのID、通話のステータスと通話時間が送られてきます。05発信に関する規則を守る
利用規定は、これらの責任を発信者、つまりお客様に課しています。以下は利用規定の要約であり、法的助言ではありません。
法律で求められる場合は、同意を得た相手にのみ発信してください。米国では、携帯電話番号へのマーケティング電話には、一般にTCPAに基づく事前の書面による明示的な同意が必要です。インドではTRAIの顧客選好規則、英国とEUではPECRとGDPRが該当します。
各アップロードの前に、国や州の発信拒否リストと自社の除外リストを確認してください。今後の電話を断られた場合は、速やかに、かつ恒久的に一覧から外してください。エージェントは丁寧に通話を終えられますが、リードを削除するのはお客様のコードの役割です。
法律で求められる場合は、上の冒頭のあいさつのように、通話の最初に発信者が自動であることを伝えてください。また、発信時間帯の制限を守り、同意の証拠を法律で求められる期間保管してください。
Wixzel Voiceでこの構築にかかる費用は?
classicエンジンで2分の通話を行った場合のエンジン利用料で、接続1分あたり$0.0867、1通話あたり約$0.17です。 電話回線はお客様自身のトランクを通信事業者の料金で使うため、ここには含まれません。
| 月間の通話数 | 接続時間(分) | 月額のエンジン費用 |
|---|---|---|
| 1,000 | 2,000 | $173 |
| 5,000 | 10,000 | $867 |
| 20,000 | 40,000 | $3,468 |
課金されるのは接続された時間だけで、秒単位です。話し中、応答なし、発信前に拒否された通話はエンジンが起動しないため無料です。回線料金は通信事業者が独自の料金で別途請求し、上乗せはありません。
前払い制で、サブスクリプションも無料プランもありません。$5からクレジットを追加すると、使用量に応じて消費されます。自分の使い方で見積もる。
この構築に関係するWixzel Voiceの制限は?
- キャンペーンはリードに立て続けに発信し、同時通話数の上限(デフォルトは1アカウントあたり5件)を超えた分をキューに入れません。上限を超えたリードは失敗として記録されます。上限に合わせてバッチに分けるか、上限の引き上げを申請してください。
- 停止したキャンペーンを再び開始すると、すべてのリードに再度発信します。まだ連絡していないリードで新しいキャンペーンを作成して続けてください。
- 1つのキャンペーンには最大10,000件のリードを登録でき、1つのアカウントにはデフォルトで最大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エージェントや、Webアプリやモバイルアプリの中で人と話すAIエージェントを構築するための音声AI APIです。1つのAPIキーと1つの前払い残高ですべての音声エンジンを利用でき、実際の使用量に応じて秒単位で課金されます。
その他のガイド
Claude Codeまたはclaude.aiのClaudeが、ご自身のSIPトランク経由で実際に電話をかけ、文字起こしを読み上げます。発信の前には必ず確認が入ります。
Node.jsで作った、既存のTwilio番号に応答してAPIに予約を登録するAI受付。
Sarvamのインドの言語向けモデルで動く、ヒンディー語とマラヤーラム語の2つの電話エージェント。ご自身のSIPトランク経由で着信に応答したり発信したりします。
ユーザーがWebSocket経由で音声エージェントと話せるFlutterアプリ。APIキーはサーバーに置いたままで、電話番号は使いません。
4つのcURLリクエストで、ご自身のSIPトランク経由で実際の電話を鳴らすGemini Liveのエージェントと、文字起こしと料金を含むその通話記録。
Webサイトの隅にいて、訪問者がブラウザから通話できる話すマスコット。指定したドメインと1日の上限の範囲内で動き、すべての発信者が一覧されます。
リアルタイム通話中にエージェントの声に合わせて口を動かすマスコットを表示するFlutterの画面。APIキーはサーバーに置いたままです。
最初の音声エージェントを構築

