---
title: "How to Build Outbound Voice Campaigns with ElevenLabs Batch Calling"
description: "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."
canonical: https://callsphere.ai/blog/vw1h-build-outbound-voice-campaigns-elevenlabs-batch-calling
category: "AI Voice Agents"
tags: ["Tutorial", "Build", "ElevenLabs", "Outbound", "Twilio"]
author: "CallSphere Team"
published: 2026-04-17T00:00:00.000Z
updated: 2026-05-07T06:45:02.780Z
---

# 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  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,
    },
  },
}));

const r = await fetch("[https://api.elevenlabs.io/v1/convai/batch-calling/submit](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:

```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](/pricing); [demo](/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

- [ElevenLabs Batch Calling](https://elevenlabs.io/blog/introducing-batch-calling-for-elevenlabs-conversational-ai)
- [ElevenLabs outbound Twilio docs](https://elevenlabs.io/docs/conversational-ai/phone-numbers/twilio-integration/outbound-calling)
- [FCC TCPA compliance guide](https://www.fcc.gov/consumers/guides/stop-unwanted-robocalls-and-texts)
- [DNC registry](https://www.donotcall.gov/)

---

Source: https://callsphere.ai/blog/vw1h-build-outbound-voice-campaigns-elevenlabs-batch-calling
