How to Build Outbound Voice Campaigns with ElevenLabs Batch Calling
By Sagar Shankaran, Founder of CallSphere
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.
Key takeaways
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 $49/$99/$149; 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

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.