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
- ElevenLabs Conversational AI agent + Twilio phone number imported.
ELEVENLABS_API_KEY,AGENT_ID,AGENT_PHONE_NUMBER_ID.- Node 20+, Postgres with leads and campaign tables.
- A public webhook URL (Cloudflare Tunnel works — see post 9).
- 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.
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_idin 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
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.