Skip to content
AI Voice Agents
AI Voice Agents11 min read0 views

How to Build Outbound Voice Campaigns with ElevenLabs Batch Calling

Run 1,000+ outbound voice calls with one API call. ElevenLabs batch calling, dynamic variables per recipient, and a follow-up loop that pushes outcomes to your CRM.

TL;DR — ElevenLabs Batch Calling fans out one agent across thousands of phone numbers in parallel, each with per-recipient variables (name, balance, appointment time). Pair it with a webhook that ingests outcomes into your CRM and you have a full outbound campaign loop in <100 lines.

What you'll build

A Node.js script that pulls 1,000 leads from Postgres, fires a single batch-calling request to ElevenLabs, and ingests post-call webhooks (call_completed, call_failed, transcript) into a campaign_event table. Each call uses dynamic variables so the agent says "Hi {{first_name}}, this is about your {{topic}}".

Prerequisites

  1. ElevenLabs Conversational AI agent + Twilio phone number imported.
  2. ELEVENLABS_API_KEY, AGENT_ID, AGENT_PHONE_NUMBER_ID.
  3. Node 20+, Postgres with leads and campaign tables.
  4. A public webhook URL (Cloudflare Tunnel works — see post 9).
  5. Compliance check on TCPA/quiet hours/DNC list before any outbound.

Architecture

flowchart LR
  DB[(leads)] --> J[Job script]
  J -->|POST /batch-calling/submit| EL[ElevenLabs]
  EL -->|outbound calls| LEADS[Recipients]
  EL -->|webhook| WH[/api/webhook]
  WH --> EVT[(campaign_event)]

Step 1 — Compliance gate (run BEFORE any code)

  • Filter against the FTC DNC list and your internal suppression list.
  • Confirm TCPA consent for each number (existing customer or written opt-in).
  • Restrict to 8am–9pm in the recipient's local time zone.
  • Add a "press 9 to stop" prompt per FCC rules.

Step 2 — Lead pull

```ts import { PrismaClient } from "@prisma/client"; const db = new PrismaClient();

const leads = await db.lead.findMany({ where: { status: "qualified", optedOut: false, lastContactedAt: { lt: new Date(Date.now() - 7 * 86400_000) }, }, select: { id: true, phone: true, firstName: true, topic: true }, take: 1000, }); ```

Step 3 — Batch payload

ElevenLabs accepts a list of recipients with per-recipient variables:

```ts const recipients = leads.map(l => ({ phone_number: l.phone, conversation_initiation_client_data: { dynamic_variables: { first_name: l.firstName, topic: l.topic, lead_id: l.id, }, }, }));

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →

const r = await fetch("https://api.elevenlabs.io/v1/convai/batch-calling/submit", { method: "POST", headers: { "xi-api-key": process.env.ELEVENLABS_API_KEY!, "content-type": "application/json", }, body: JSON.stringify({ call_name: `renewal-${new Date().toISOString().slice(0,10)}`, agent_id: process.env.AGENT_ID!, agent_phone_number_id: process.env.AGENT_PHONE_NUMBER_ID!, scheduled_time_unix: Math.floor(Date.now() / 1000), recipients, }), }); const { batch_id } = await r.json(); console.log("submitted batch:", batch_id); ```

Step 4 — Reference dynamic variables in the agent prompt

In the ElevenLabs agent system prompt:

```text You are calling {{first_name}} about their {{topic}}. Open with: "Hi {{first_name}}, this is CallSphere following up on your {{topic}} — do you have a moment?" If they say no, schedule a callback. If they say yes, run the renewal flow. ```

Step 5 — Post-call webhook receiver

Configure the webhook URL in your ElevenLabs agent's "post-call webhooks" setting. Then:

```ts import express from "express"; const app = express(); app.use(express.json());

app.post("/webhook/elevenlabs", async (req, res) => { const { event_type, conversation_id, data } = req.body; if (event_type === "post_call_transcription") { await db.campaignEvent.create({ data: { conversationId: conversation_id, leadId: data.metadata?.dynamic_variables?.lead_id, outcome: data.analysis?.call_successful ? "success" : "failed", transcript: data.transcript, durationSec: data.metadata?.call_duration_secs, rawData: data, }, }); if (data.analysis?.call_successful) { await db.lead.update({ where: { id: data.metadata.dynamic_variables.lead_id }, data: { status: "contacted", lastContactedAt: new Date() }, }); } } res.sendStatus(200); }); app.listen(8080); ```

Step 6 — Verify webhook signatures

ElevenLabs signs webhooks with HMAC. Verify before trusting the body:

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

```ts import crypto from "crypto"; function verify(sig: string, body: string, secret: string) { const h = crypto.createHmac("sha256", secret).update(body).digest("hex"); return crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(h)); } ```

Step 7 — Throttling and retries

Batch calling has built-in pacing, but for very large lists split into batches of 5,000 and stagger by hour. Retry failed numbers (busy/no-answer) once after 24 hours; never more than 3 attempts per FCC guidance.

Common pitfalls

  • Skipping the DNC scrub: penalties are $1,500+ per call.
  • Calling outside 8am–9pm local: TCPA violation.
  • Not capturing lead_id in dynamic variables: webhooks land orphaned.
  • Hot-spinning retries: ElevenLabs returns 429 if you push too fast — respect Retry-After.

How CallSphere does this in production

CallSphere's outbound is multi-provider (ElevenLabs + OpenAI Realtime via Twilio) routed by vertical. Salon nudges (rebook reminders) use ElevenLabs batch calling with GB-YYYYMMDD-### booking refs threaded through dynamic variables. Healthcare uses outbound only for confirmed-consent appointment reminders, never cold. Pricing $149/$499/$1499; demo.

FAQ

TCPA risk? Real. Scrub DNC, honor opt-outs in <24h, log consent timestamps.

Throughput? ElevenLabs handles thousands per batch; Twilio is the bottleneck (typically 1 call/sec/number — buy more numbers for parallelism).

Cost per call? Roughly $0.05–$0.15/min ElevenLabs + Twilio carrier fees.

Can I A/B test prompts? Yes — run two batches with different agent IDs and compare call_successful rates.

Sources

Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.