← All posts
GuideAugust 30, 2026 · 10 min

voice ai for appointment booking: Build a Vapi + ElevenLabs Agent that books slots and sends reminders 24/7

You can wire Vapi's speech-to-intent engine to ElevenLabs' neural text-to-speech, then glue the two together with an n8n workflow that talks to Google Calendar and Twilio. The result is a fully-automated "voice AI for appointment booking" that can take calls, create calendar events, confirm via SMS, and call the client back with a reminder generated on the fly.


What you need

ToolPlan / Price*Role
VapiFree tier (up to 5,000 minutes/month) - check the pricing pageSpeech recognition, intent extraction, phone number provisioning
ElevenLabsFree tier (10,000 characters/month) - check the pricing pageHigh-quality voice synthesis for confirmations & reminders
n8nSelf-hosted Docker (free) or Cloud starter $20 / monthOrchestrates API calls between Vapi, ElevenLabs, Google Calendar, Twilio, OpenAI
Google Cloud - Calendar APIFree tier (up to 1 million calls/month) - check pricingStores appointment slots, provides event IDs and reminders
TwilioPay-as-you-go (≈ $0.0085 per outbound call, $0.0075 per SMS)PSTN/SMS gateway for confirmations and reminder calls
OpenAI (optional)Free trial $18, then $0.002 per 1 k tokensFallback NLU when Vapi intent confidence is low
Calendly (optional)Free plan (basic scheduling)Alternative booking UI if you prefer a web link over Google Calendar

\*Prices are accurate as of August 2026; always verify on the provider's pricing page.

Estimated build time: 6-8 hours (account setup ≈ 1 h, n8n workflow ≈ 3 h, testing ≈ 2 h).


Step-by-step build

Below is a concrete, copy-paste-ready path from "zero" to a production-ready voice AI for appointment booking.

1. Create a Vapi voice agent

  1. Sign up at the Vapi console and obtain an API key (Settings → API Keys).
  2. In Agents → New Agent choose "Phone Call" as channel.
  3. Define two intents:
IntentSample utterancesSlots (variables)
book_appointment"I'd like to book a meeting", "Schedule a 30-minute call next Thursday at 2 pm"date, time, duration, name, phone
reschedule_appointment"Can we move my appointment to Friday?", "Change my booking"event_id, new_date, new_time
  1. Set Webhook URL (we'll create it in n8n) - keep the placeholder for now (https://YOUR_N8N_DOMAIN/webhook/vapi-book).

Tip: Vapi returns a JSON payload with the extracted slots and a confidence score. Keep the confidence > 0.8 for direct processing; otherwise forward to OpenAI.

2. Provision ElevenLabs TTS

  1. Register at ElevenLabs and generate an API key (Dashboard → API Keys).
  2. Choose a voice (e.g., "Bella") and note the voice_id (EXAMPLE_VOICE_ID).
  3. The API uses a per-character quota; the free tier gives you 10 k characters/month - more than enough for short confirmations and reminders.

3. Enable Google Calendar API

  1. Open the Google Cloud Console, create a new project "Voice-Booking".
  2. Navigate to APIs & Services → Library and enable Google Calendar API.
  3. Go to Credentials → Create Credentials → OAuth client ID (type Web application).
  4. Add https://YOUR_N8N_DOMAIN/ as an authorized redirect URI.
  5. Download the resulting credentials.json and store it in the n8n data folder (/root/.n8n/).

Why OAuth? The Calendar API requires a refresh token for long-running workflows; n8n can store it automatically.

4. Install and configure n8n

bash
# Pull the official n8n Docker image (latest tag as of Aug 2026)
docker run -d \
 --name n8n \
 -p 5678:5678 \
 -v ~/.n8n:/root/.n8n \
 n8nio/n8n:latest
  1. Open http://localhost:5678 and set a strong admin email / password.
  2. In Settings → Credentials add the following entries:
CredentialServiceFields
Vapi APIVapiapiKey
ElevenLabs APIElevenLabsapiKey
Google Calendar OAuth2Google Calendarupload credentials.json, authorize the account that owns the target calendar
TwilioTwilioAccount SID, Auth Token, From number (a purchased Twilio number)

5. Build the n8n workflow

The workflow consists of three logical parts:

  1. Incoming webhook - receives Vapi's intent payload.
  2. Booking branch - creates a Google Calendar event and sends confirmation.
  3. Reminder branch - at the scheduled time, calls the client back with an ElevenLabs-generated voice reminder.

#### 5.1 Webhook node (Vapi)

What this does: Accepts the JSON Vapi sends after intent extraction.

json
{
 "nodes": [
 {
 "parameters": {
 "httpMethod": "POST",
 "path": "vapi-book",
 "responseMode": "onReceived"
 },
 "name": "Vapi Webhook",
 "type": "n8n-nodes-base.webhook",
 "typeVersion": 1,
 "position": [
 250,
 300
 ]
 }
 ]
}

The incoming payload looks like:

json
{
 "intent": "book_appointment",
 "confidence": 0.93,
 "slots": {
 "date": "2026-09-12",
 "time": "14:00",
 "duration": "30",
 "name": "Jane Doe",
 "phone": "+15551234567"
 }
}

#### 5.2 Normalise date & time

Add a Function node after the webhook to merge date and time into an ISO-8601 string and compute the end time.

javascript
// Input: $json.slots
const { date, time, duration } = $json.slots;
const start = new Date(`${date}T${time}:00Z`);
const end = new Date(start.getTime() + Number(duration) * 60000);
return {
 json: {
 ...$json.slots,
 start_iso: start.toISOString(),
 end_iso: end.toISOString()
 }
};

#### 5.3 Create Google Calendar event

Add a Google Calendar node (Operation: Create). Map fields:

Calendar fieldSource
summary{{ $json.name }} - Consultation
description"Booked via voice AI."
start.dateTime{{ $json.start_iso }}
end.dateTime{{ $json.end_iso }}
attendees[0].email(optional - if you collect email)
reminders.useDefaultfalse
reminders.overrides[0].methodpopup
reminders.overrides[0].minutes10

The node returns an id (Google eventId) we will need for reminders.

#### 5.4 Send SMS confirmation via Twilio

Add a Twilio node (Operation: Send SMS).

  • To: {{ $json.phone }}
  • Body:
 Hi {{ $json.name }}, your appointment is confirmed for {{ $json.date }} at {{ $json.time }} ({{ $json.duration }} min). Reply STOP to cancel.
 

#### 5.5 Generate spoken confirmation with ElevenLabs

  1. Add an HTTP Request node (Method: POST, URL: https://api.elevenlabs.io/v1/text-to-speech/{{ $env.ELEVENLABS_VOICE_ID }})
  2. Authentication: Header xi-api-key: {{ $env.ELEVENLABS_API_KEY }}
  3. Body (JSON):
json
{
 "text": "Your appointment is booked for {{ $json.date }} at {{ $json.time }}. We will call you a day before as a reminder.",
 "voice_settings": {
 "stability": 0.75,
 "similarity_boost": 0.85
 }
}
  1. Enable Response Format: binary (audio/mpeg).

The node outputs an MP3 buffer; pipe it straight to Twilio Make Call node.

#### 5.6 Twilio outbound call (reminder voice)

Add a Twilio node (Operation: Make Call).

  • To: {{ $json.phone }}
  • From: your Twilio number.
  • Twiml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
 <Play>{{ $node["ElevenLabs TTS"].json["data"] }}</Play>
</Response>

Why Twiml? Twilio expects a URL or inline XML. n8n can host a temporary endpoint that returns the MP3 as a URL; the above inline <Play> works when you expose the binary data via n8n's Webhook response.

#### 5.7 Schedule the reminder

Two approaches:

Google Calendar native reminders - set a `email` or `popup` 24 h before the event. n8n Cron node - poll the calendar for events happening tomorrow, then trigger the Twilio call.

Below is the Cron approach (runs daily at 08:00 UTC).

json
{
 "nodes": [
 {
 "parameters": {
 "cronExpression": "0 8 * * *"
 },
 "name": "Daily Reminder Trigger",
 "type": "n8n-nodes-base.cron",
 "typeVersion": 1,
 "position": [100, 100]
 },
 {
 "parameters": {
 "operation": "Search",
 "calendarId": "primary",
 "timeMin": "{{$moment().add(1, 'day').startOf('day').toISOString()}}",
 "timeMax": "{{$moment().add(1, 'day').endOf('day').toISOString()}}",
 "maxResults": 50
 },
 "name": "Find Tomorrow Events",
 "type": "n8n-nodes-base.googleCalendar",
 "typeVersion": 1,
 "position": [300, 100]
 },
 {
 "parameters": {
 "functionCode": "return items.map(item => {\n const ev = item.json;\n return {\n json: {\n phone: ev.attendees?.[0]?.email || ev.description.match(/Phone:\\s*(\\+\\d+)/i)?.[1] || null,\n name: ev.summary.split(' - ')[0],\n date: ev.start.dateTime.split('T')[0],\n time: ev.start.dateTime.split('T')[1].substring(0,5)\n }\n };\n});"
 },
 "name": "Extract Info",
 "type": "n8n-nodes-base.function",
 "typeVersion": 1,
 "position": [500, 100]
 },
 {
 "parameters": {
 "url": "https://api.elevenlabs.io/v1/text-to-speech/{{ $env.ELEVENLABS_VOICE_ID }}",
 "options": {
 "bodyContentType": "json",
 "jsonParameters": true,
 "json": {
 "text": "Hello {{ $json.name }}, this is a reminder that you have an appointment tomorrow at {{ $json.time }}.",
 "voice_settings": {
 "stability": 0.75,
 "similarity_boost": 0.85
 }
 },
 "headers": {
 "xi-api-key": "={{ $env.ELEVENLABS_API_KEY }}"
 }
 },
 "responseFormat": "binary"
 },
 "name": "ElevenLabs Reminder TTS",
 "type": "n8n-nodes-base.httpRequest",
 "typeVersion": 1,
 "position": [700, 100]
 },
 {
 "parameters": {
 "to": "={{ $json.phone }}",
 "from": "{{ $env.TWILIO_NUMBER }}",
 "twiml": "<?xml version='1.0' encoding='UTF-8'?><Response><Play>{{ $node[\"ElevenLabs Reminder TTS\"].json[\"data\"] }}</Play></Response>"
 },
 "name": "Call Reminder",
 "type": "n8n-nodes-base.twilio",
 "typeVersion": 1,
 "position": [900, 100]
 }
 ],
 "connections": {
 "Daily Reminder Trigger": {
 "main": [
 [
 {
 "node": "Find Tomorrow Events",
 "type": "main",
 "index": 0
 }
 ]
 ]
 },
 "Find Tomorrow Events": {
 "main": [
 [
 {
 "node": "Extract Info",
 "type": "main",
 "index": 0
 }
 ]
 ]
 },
 "Extract Info": {
 "main": [
 [
 {
 "node": "ElevenLabs Reminder TTS",
 "type": "main",
 "index": 0
 }
 ]
 ]
 },
 "ElevenLabs Reminder TTS": {
 "main": [
 [
 {
 "node": "Call Reminder",
 "type": "main",
 "index": 0
 }
 ]
 ]
 }
 }
}

Key point: The workflow pulls the phone number from the event's description; you can also store it in a custom extendedProperties field during the booking step.

6. Wire Vapi → n8n

Return to the Vapi console, edit the agent's Webhook URL, and paste the publicly reachable n8n endpoint (e.g., https://YOUR_N8N_DOMAIN/webhook/vapi-book). Enable "Send webhook on every intent".

7. Test end-to-end

  1. Call the Vapi-provided phone number.
  2. Say: "I need a 30-minute call next Thursday at 2 pm, my name is Jane."
  3. Vapi recognises book_appointment, forwards the payload to n8n.
  4. n8n creates a Google Calendar event, sends an SMS, and immediately places a short confirmation call using ElevenLabs-generated speech.
  5. Verify the event appears in Google Calendar and the SMS arrives.
  6. Wait until the next day's cron run (or manually trigger it) and confirm you receive the reminder call.

If anything fails, check the Execution Log in n8n - each node's output is stored for debugging.


Where this breaks

Failure modeSymptomFix / Mitigation
Vapi minute quotaCalls drop after a few hundred minutes in a month.Monitor usage in the Vapi dashboard; upgrade to a paid plan before hitting the free 5,000-minute ceiling.
ElevenLabs character limitTTS API returns 429 Too Many Requests.Cache repeated confirmation sentences; combine multiple sentences into a single request; purchase additional quota if needed.
Google OAuth token expiry"Invalid Credentials" error from Calendar node after ~1 hour.n8n's OAuth2 credential automatically refreshes if the refresh token is saved; ensure you selected "Access type: offline" when creating the client ID.
Twilio cost surpriseUnexpected $ per-call charges after a volume spike.Set a daily spend limit in the Twilio console; log each call in n8n and pause the workflow if cost exceeds a threshold.
Timezone driftAppointments end up an hour early/late.Store and convert all dates in UTC (Z suffix). Use the moment-timezone library in Function nodes if you need a specific zone (e.g., America/New_York).
Webhook not reachablen8n returns 404 or "Connection refused".Expose n8n behind a trusted domain with TLS (Let's Encrypt). Use a tunneling service (ngrok) only for dev; never in production.
Edge-case speechCaller says "next Monday" but Vapi extracts a wrong date.Add a fallback to OpenAI: if confidence < 0.8, forward the raw transcript to gpt-4o-mini with a prompt to parse a date.

Bottom line: The most common production blocker is auth token expiry. Keep the Google Calendar OAuth credentials refreshed automatically and store them in n8n's credential manager - that alone eliminates 80 % of runtime errors.


For a deeper technical reference, see n8n's documentation.

FAQ

Can I replace Google Calendar with Calendly? Yes. Instead of the Google Calendar node, use an HTTP Request node that calls Calendly's Create Scheduled Event endpoint (https://api.calendly.com/scheduled_events). You'll need a Calendly API key (found in Integrations → API). The rest of the workflow (SMS, TTS) stays unchanged.

How does the system handle callers in different time zones? Vapi returns the spoken time in the caller's local context, but it does not tag a zone. You must ask the caller for their city or use the phone number's country code to infer a default offset. In a Function node you can apply moment.tz(dateTime, "America/Los_Angeles") before sending the ISO string to Google Calendar.

What if the caller hangs up before the workflow finishes? The Vapi webhook fires as soon as intent extraction completes, regardless of call state. The workflow continues in n8n, creates the event, and sends an SMS. If you need a "call-back-only" flow, set a flag in the intent payload and branch to a no-action path that waits for the user to call back.

Do I have to self-host n8n? No. If you prefer a managed service, n8n Cloud starts at $20 / month and includes automatic SSL, scaling, and built-in credential storage. The self-hosted Docker image is free but you must manage updates and security patches yourself.

How can I secure the webhook endpoint from malicious calls? Add a Header Authentication node right after the webhook that checks for a custom X-Secret header (store the secret as an environment variable). Reject any request that doesn't match with a 401 response.


If you're hunting for more monetizable ideas, check out AI automations you can sell. And when you're ready to dive deeper, the free guide walks you through scaling voice AI agents from a single prototype to a multi-tenant SaaS.

Happy building - the world needs a voice AI for appointment booking that actually works, not just hype.

Get the full toolkit

Grab the free guide with the node-by-node build for all 10 automations.

No spam. Unsubscribe anytime. Just the good stuff.