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
| Tool | Plan / Price* | Role |
|---|---|---|
| Vapi | Free tier (up to 5,000 minutes/month) - check the pricing page | Speech recognition, intent extraction, phone number provisioning |
| ElevenLabs | Free tier (10,000 characters/month) - check the pricing page | High-quality voice synthesis for confirmations & reminders |
| n8n | Self-hosted Docker (free) or Cloud starter $20 / month | Orchestrates API calls between Vapi, ElevenLabs, Google Calendar, Twilio, OpenAI |
| Google Cloud - Calendar API | Free tier (up to 1 million calls/month) - check pricing | Stores appointment slots, provides event IDs and reminders |
| Twilio | Pay-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 tokens | Fallback 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
- Sign up at the Vapi console and obtain an API key (Settings → API Keys).
- In Agents → New Agent choose "Phone Call" as channel.
- Define two intents:
| Intent | Sample utterances | Slots (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 |
- 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
- Register at ElevenLabs and generate an API key (Dashboard → API Keys).
- Choose a voice (e.g., "Bella") and note the voice_id (
EXAMPLE_VOICE_ID). - 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
- Open the Google Cloud Console, create a new project "Voice-Booking".
- Navigate to APIs & Services → Library and enable Google Calendar API.
- Go to Credentials → Create Credentials → OAuth client ID (type Web application).
- Add
https://YOUR_N8N_DOMAIN/as an authorized redirect URI. - Download the resulting
credentials.jsonand 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
- Open
http://localhost:5678and set a strong admin email / password. - In Settings → Credentials add the following entries:
| Credential | Service | Fields |
|---|---|---|
Vapi API | Vapi | apiKey |
ElevenLabs API | ElevenLabs | apiKey |
Google Calendar OAuth2 | Google Calendar | upload credentials.json, authorize the account that owns the target calendar |
Twilio | Twilio | Account SID, Auth Token, From number (a purchased Twilio number) |
5. Build the n8n workflow
The workflow consists of three logical parts:
- Incoming webhook - receives Vapi's intent payload.
- Booking branch - creates a Google Calendar event and sends confirmation.
- 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.
The incoming payload looks like:
#### 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.
#### 5.3 Create Google Calendar event
Add a Google Calendar node (Operation: Create). Map fields:
| Calendar field | Source |
|---|---|
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.useDefault | false |
reminders.overrides[0].method | popup |
reminders.overrides[0].minutes | 10 |
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:
#### 5.5 Generate spoken confirmation with ElevenLabs
- Add an HTTP Request node (Method: POST, URL:
https://api.elevenlabs.io/v1/text-to-speech/{{ $env.ELEVENLABS_VOICE_ID }}) - Authentication: Header
xi-api-key: {{ $env.ELEVENLABS_API_KEY }} - Body (JSON):
- 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:
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).
Key point: The workflow pulls the phone number from the event's description; you can also store it in a custom
extendedPropertiesfield 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
- Call the Vapi-provided phone number.
- Say: "I need a 30-minute call next Thursday at 2 pm, my name is Jane."
- Vapi recognises
book_appointment, forwards the payload to n8n. - n8n creates a Google Calendar event, sends an SMS, and immediately places a short confirmation call using ElevenLabs-generated speech.
- Verify the event appears in Google Calendar and the SMS arrives.
- 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 mode | Symptom | Fix / Mitigation |
|---|---|---|
| Vapi minute quota | Calls 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 limit | TTS 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 surprise | Unexpected $ 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 drift | Appointments 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 reachable | n8n 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 speech | Caller 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.