Home › AI Voice Receptionist (Vapi + n8n)

Build teardown

AI 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.

By Patrick Sales · AI Automation Specialist · Built and running on self-hosted n8n

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:

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."

  1. 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.
  2. Build candidates Outputs the staff list in round-robin order starting at rowCount % 3, each carrying their calendar id and the requested slot.
  3. Check every candidate's real calendar One availability call per person for that exact slot.
  4. Pick the first genuinely free person Zips candidates against availability and returns the first open one — or a polite "try another time".
  5. 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:

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.

CostShapeNotes
Phone numberFixed, ~$1–2/moPer number, per month
Telephony minutesPer minute, centsInbound is cheap; outbound varies a lot by destination country
Realtime voice platformPer minuteOnly 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 tokensPer turngpt-4o-mini keeps this close to noise for short calls
n8n hostingFixed, smallSelf-hosted on a small instance covers many clients at once
The buildOne-offYou 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.

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