Home › AI Voice Receptionist (Vapi + n8n)
Build teardownAI Voice Receptionist with Vapi + n8n — how it works and what it costs
Not a demo that answers one question. A phone number that answers, understands, checks a real calendar, books the appointment, texts a confirmation, and hands the call to a human when it should. Here is the whole thing, node by node — including the parts that broke.
The short version
- Six small workflows, not one big one — a greeting, a brain, a booking engine, an outbound dialer, and two Vapi adapters.
- The brain is one AI agent shared by inbound and outbound, so qualifying, booking and transfer logic exists exactly once.
- Booking is the hard part: round-robin across staff plus a real availability check, or you will double-book people.
- Runs on OpenAI gpt-4o-mini. Cost is per-minute, not per-seat — and the business owns the system outright.
The architecture
A caller dials your number. Two things can be on the other end of it, and the choice matters more than any other decision in the build.
Twilio number ──▶ Inbound Greeting ──▶ AI Voice Agent ──┬──▶ Smart Booking (own webhook)
└──▶ Dial a human (transfer)
Lead sheet ──────▶ Outbound Call ──────▶ (the same AI Voice Agent brain)
Vapi assistant ──▶ Booking Adapter ────▶ (the same Smart Booking webhook)
└──▶ Call Logger ────────▶ transcript + recording into a sheet
Turn-based (Twilio) vs realtime (Vapi)
With Twilio, each webhook returns TwiML — an XML instruction saying speak this, then listen. <Say> talks, <Gather input="speech"> records the caller's reply and posts it to the next webhook. It's a loop of turns. It is cheap, completely inspectable, and you can read every word the system said. The trade-off is a beat of silence between turns, and the caller can't interrupt.
With Vapi, the assistant lives on Vapi's side and streams in realtime — it handles barge-in, sounds like a conversation, and calls out to your tools when it needs to do something. You pay a per-minute platform fee for that.
The useful insight: you don't have to choose forever. The booking engine below is called over plain HTTP, so both front-ends drive the same logic. Moving from Twilio to Vapi cost one small adapter workflow, not a rebuild.
Workflow by workflow
1 — Inbound Greeting
The number points here. A webhook fires the instant the call connects and returns TwiML: a spoken greeting, then a <Gather> pointed at the agent's webhook. The greeting plays, Twilio records the caller's first sentence, and posts it onward.
Gotcha
The response header must be Content-Type: text/xml. Return it as JSON and Twilio silently refuses to parse it — the call connects and then nothing happens, with no error anywhere useful.
2 — AI Voice Agent (the brain)
Every spoken turn lands here. The webhook receives the transcribed speech and the CallSid, a unique id for that call. Three things hang off the agent node:
- OpenAI Chat Model —
gpt-4o-mini. Fast and cheap matters here; latency is audible. - Simple Memory, keyed by
CallSid— so each call has its own running memory and two simultaneous callers never bleed into each other's conversation. - A
book_appointmenttool — an HTTP request the agent can fire mid-sentence, fillingname,serviceandstartTimeitself. The system prompt tells it to read the details back and wait for a yes before calling it.
Then a single If node does the transfer logic. The system prompt says: if the caller asks for a human, reply with exactly <<TRANSFER>>. The If checks each reply for that token. If it's there, the workflow returns TwiML that dials a real person. If not, it speaks the answer and gathers again. That's the conversational loop, in one branch.
Gotcha — why booking runs over HTTP instead of a sub-workflow
On n8n 2.x, a Code node inside a sub-workflow invoked by an AI agent's "Call Sub-Workflow" tool throws a masked task-runner error — the failure surfaces nowhere near the cause. The fix was to give the booking workflow its own webhook and let the agent call it as a plain HTTP tool. Same result, no bug, and it made the Vapi upgrade trivial later.
3 — Smart Booking
This is where most AI receptionist builds fall over. Booking isn't "create a calendar event" — it's "assign the right person, and never double-book them."
- Read the bookings log A Google Sheet. The row count doubles as the round-robin counter — the log is the counter, which means it survives restarts where in-memory state wouldn't.
- Build candidates Outputs the staff list in round-robin order starting at
rowCount % 3, each carrying their calendar id and the requested slot. - Check every candidate's real calendar One availability call per person for that exact slot.
- Pick the first genuinely free person Zips candidates against availability and returns the first open one — or a polite "try another time".
- Create, log, confirm Calendar event, append the row, build the spoken confirmation, send the SMS, return the text to the agent so it can say it out loud.
Two gotchas worth the whole page
Rotation alone double-books. Pure round-robin will happily assign two callers wanting the same slot to two different people — but it will also assign one to someone already booked. The availability check plus "pick first free" is what makes the rotation safe.
Guard the model's dates. Language models will confidently produce a start time in the past. The booking code rolls any past date forward until it's in the future, so a hallucinated year can never write a booking into last March.
Let the SMS fail. The confirmation text is set to continue on error. A carrier hiccup should never roll back a real appointment.
4 — Outbound (same brain, dialling out)
Read a lead list, dial each one with TwiML that opens with a personalised line, then <Gather> into the same agent webhook. Qualifying, booking and transfer all work outbound with zero duplicated logic — which is the entire argument for splitting workflows by mode rather than by channel.
Gotcha
Google Sheets eats the leading + on phone numbers — it reads them as numbers. The dial expression has to put it back, or every outbound call fails on a malformed number.
5 & 6 — The Vapi layer
When the front desk runs on Vapi, the assistant lives in Vapi's dashboard and calls a book_appointment tool. Vapi posts that tool call to a server URL, waits, and speaks whatever comes back. Two small workflows cover it:
- Booking adapter — pulls the arguments out of Vapi's nested tool-call payload, posts them to the same booking webhook the Twilio agent uses, and wraps the answer in the
results[]shape Vapi expects. ThetoolCallIdmust match the incoming one exactly or Vapi discards the result and the caller hears silence. - Call logger — Vapi posts an end-of-call report; the workflow filters for that event type, fetches the full call record for the transcript and recording URL, flattens it into nine columns and appends a row.
Free testing trick
Vapi's API reads are free, so you can replay the entire logger without making a call: post the webhook with the id of any past call and a minimal end-of-call body. It re-fetches the real call and runs the whole chain on real data — no minutes burned debugging.
What it actually costs
The thing to understand about running cost: it's per-minute, not per-seat. A subscription receptionist product charges by location or by seat whether the phone rings or not. This charges for calls that actually happen.
| Cost | Shape | Notes |
|---|---|---|
| Phone number | Fixed, ~$1–2/mo | Per number, per month |
| Telephony minutes | Per minute, cents | Inbound is cheap; outbound varies a lot by destination country |
| Realtime voice platform | Per minute | Only if you use Vapi or similar. This is the largest per-minute line. Skip it and run turn-based Twilio to cut it entirely |
| LLM tokens | Per turn | gpt-4o-mini keeps this close to noise for short calls |
| n8n hosting | Fixed, small | Self-hosted on a small instance covers many clients at once |
| The build | One-off | You own it afterwards. No per-seat fee, no vendor holding the workflow |
For a small practice taking a few hundred calls a month, the running total usually lands in the low tens of dollars. Provider rates change often enough that you should price the current numbers before quoting anyone — but the shape is stable, and it's what makes this cheaper than a subscription for most single-location businesses.
What breaks in production
Every failure below actually happened during this build. That's the point of listing them.
- Calls bleeding together — memory not keyed per call. One caller hears another's context.
- Bookings in the past — an unguarded model-supplied date.
- Double-booked staff — rotation without an availability check.
- Silent dead air — a mismatched tool-call id, or the wrong content type on a TwiML response.
- A failed SMS killing a real booking — no error branch on a non-critical step.
None of these show up in a demo. All of them show up in month three, which is why every workflow I ship is wired into a shared error handler that catches failures and notifies rather than dying quietly.
Common questions
Can it really book into a real calendar?
Yes — that's the part demos skip. The agent gets a booking tool it can call mid-conversation; the tool checks each staff member's actual calendar for that slot, picks whoever is genuinely free, creates the event and texts a confirmation. It reads the details back and waits for a "yes" first.
Vapi or Twilio?
Twilio if cost and transparency matter most and a small pause between turns is acceptable. Vapi if it needs to sound like a real conversation with interruptions. The booking engine serves both, so this isn't a permanent decision.
What happens when the AI can't handle it?
It transfers to a human rather than guessing. The prompt tells it to emit a transfer token; the workflow watches for that token and hands the live call over. "I'll get someone who can help" beats a confident wrong answer every time.
Can it run on my existing CRM instead of Google Calendar?
Yes. The booking step is one workflow with a clear input. Pointing it at GoHighLevel's calendar API instead is a swap of that step — the conversation logic doesn't change. I've built the GoHighLevel version too.
Do I own it?
Yes, and this is the main difference from a subscription product. The workflows live in your n8n instance under your accounts, and handover includes a written node-by-node walkthrough so your team can change it without me.
Want this on your number?
Tell me what your front desk deals with all day — plain English is fine. I'll tell you what it would take, roughly what it would cost, and whether it's worth doing at all.
Related
- Custom AI receptionist vs a subscriptionWhen building beats renting — and when it doesn't
- GoHighLevel missed-call text-backThe full build, and where the stock feature stops
- AI automation specialist — what I buildServices, stack and how projects run
- The workflows on GitHubEvery build, with a node-by-node walkthrough