Skip to content
All projects
Dealership Service

Voice AI Service Line

An inbound and outbound voice AI system for a dealership service department, built on VAPI for call handling and n8n for the backend logic. The assistant books, reschedules, and looks up service history live during the call, while n8n runs outbound reminder campaigns and logs every call outcome, without a human touching either direction.

Sample

This project was designed and built independently, using a realistic business scenario and sample data, not live client work. It's here to show how I think through a problem and build the solution end to end.

The Problem

Dealership service departments lose business in ways that have nothing to do with the quality of their work. The phone rings while every advisor is with a customer, so it goes to voicemail or rings out. No one has time to call customers due for routine service or sitting on an open safety recall, so those visits never get booked unless the customer thinks of it first. And when calls do happen, there's often no consistent record of what was discussed.

Each of these costs a booking or creates a compliance gap. A voice AI system that actually executes bookings and lookups, not just talks, closes all three at once.

The Solution

Two VAPI voice assistants handle the dealership's service department: an inbound assistant that answers the service line, and an outbound assistant that places reminder calls, with voicemail detection tuned for reminder calls specifically. Three n8n workflows do the real work behind both: a tool webhook router that looks up customers, checks for duplicate bookings, creates a customer record for first-time callers, and books or changes appointments; a scheduled campaign that finds customers due for service or sitting on an open recall and calls them; and a call outcome logger that writes a summary of every call back into a shared Google Sheet acting as the CRM. Either assistant can hand off to a live service advisor mid-call when something falls outside routine scheduling.

A long list of real bugs, found while testing this rather than just designing it, shaped most of the decisions covered below.

The Result

Testing was done through VAPI's browser-based Test call feature, not a live phone number. A Test call exercises the exact same assistant, prompt, and n8n webhook a real phone call would, so the logic itself is proven; an actual phone ringing end to end isn't. That's a real gap, and the clear next step for this build.

All 9 scenarios in the test plan pass: new-customer lookup, an existing customer with an open recall surfaced proactively, booking, rescheduling, duplicate-booking prevention, new-customer capture, human handoff (confirmed as the assistant's decision to escalate, not a completed live transfer), the outbound campaign's filtering and call-payload logic, and a deliberate failure test confirming the assistant fails gracefully instead of going silent.

The more interesting result is the range of real bugs this surfaced, several of them the kind that fail silently rather than throwing a clear error (see Engineering Decisions above). A build that only demonstrates the happy path doesn't show much about how someone works through a real integration. This one does.

Architecture

Five independent services work together to automate dealership calls from the first ring to the final logged outcome. Select a service to explore how it fits into the system.

This case study uses Google Sheets as the CRM to keep the demonstration lightweight. The architecture is intentionally CRM-agnostic and can integrate with platforms such as Salesforce, HubSpot, Zoho CRM, or any API-enabled CRM by replacing the data layer without changing the core automation workflows.

Explore the system

Every service reads and writes the shared CRM.

How It Works

  1. 1

    A caller reaches the service line, or an outbound reminder call connects. The assistant gets the caller's name and phone number early in the call.

  2. 2

    It calls lookup_customer against the shared Google Sheet through n8n. If the vehicle has an open recall, it's surfaced immediately, without the caller having to ask.

  3. 3

    To book, it calls check_availability for the requested date and service type, offers real open slots (never invented), then calls book_appointment once the caller confirms a time. n8n checks for a duplicate booking first, then creates a customer record automatically if this is their first time calling in.

  4. 4

    Reschedules and cancellations go through update_appointment the same way, matched to an existing appointment ID.

  5. 5

    If the call involves a warranty dispute, a pricing negotiation, or the caller is upset or explicitly asks for a person, the assistant uses a built-in transferCall tool to connect to a live service advisor instead of trying to resolve it.

  6. 6

    Once the call ends, VAPI sends an end-of-call-report to n8n's Call Outcome Logger, which logs the outcome and an AI-generated summary, whether the call was inbound or outbound.

  7. 7

    A daily n8n schedule reads the same Customers sheet, filters to anyone with an open recall or overdue for service, and places a short outbound reminder call through the same tool-calling setup, with voicemail handled gracefully if no one picks up.

Engineering Decisions

The choices that mattered most, and the reasoning behind them.

The documented API didn't match the live one

VAPI's docs put a tool call's name and arguments flat on the object, but the live payload nests them under a function key. That mismatch broke every call with a vague "tool not recognized," so I checked the raw webhook payload and read the nested path instead.

A JavaScript reserved word silently blocked half the booking flow

n8n's expression evaluator won't resolve a field named arguments, since it collides with JavaScript's own arguments object. The booking and rescheduling branches read it straight into their Sheets nodes, so I pulled those values into renamed fields inside a Code node first.

Phone-number formatting broke matching twice, on both sides

Google Sheets strips the leading plus from phone numbers, so an exact-string match failed on inbound lookups and on outbound calls, where VAPI rejects a number with no country code. I fixed both by matching on digits only and adding the country code back when it's missing.

Call logging assumed every call already had a row to update

The logger only knew how to update an existing row. That worked for outbound calls, where the campaign appends a placeholder before dialing, but inbound calls logged nothing because no row existed yet. Now it checks first, then updates a match or appends a new row.

A first-time caller could book without becoming a customer, and could double-book

Testing real bookings surfaced two gaps: a first-time caller booked fine but was never saved as a customer, and nothing stopped a repeat booking for the same slot. One branch fixes both, checking for a duplicate first, then creating the customer only if they're new.

n8n-driven outbound calling instead of VAPI's native campaign feature

VAPI's built-in bulk campaign needs an imported Twilio, Vonage, or Telnyx number. I built the outbound side in n8n instead: it filters the CRM on real conditions like open recalls and days since service, then calls VAPI's raw call endpoint per customer.

Google Sheets instead of a real dealership DMS

A real dealership runs on a closed platform. A Google Sheet is easy to seed with mock data and quick to inspect and screenshot, and a production build would connect to the dealership's DMS or CRM through its API instead.

Reliability, Security & Scale

Error Handling

The router falls back on an unknown tool name, and the outbound campaign continues past a failed call instead of halting the batch, both confirmed by a deliberate test. VAPI's own side is harder: an outage never reaches n8n, and a malformed report logs a blank row. A reconciliation job against VAPI's call log would close both.

Security & Data Handling

Both directions read and write one Google Sheet, and the duplicate check plus automatic new-customer creation keep one consistent record. Voice collects only a name and phone number, held in a plain sheet with no access control, fine for mock data. n8n keeps its one credential encrypted, but the webhook VAPI calls has no shared secret or signature check, which production would need.

Logging & Monitoring

VAPI pushes an end-of-call report to the logger the moment a call ends, so n8n never polls. The logger appends a fresh row for inbound calls and updates the placeholder an outbound call already carries from when the campaign dialed it. Monitoring is the sheet itself, so a stuck "initiated" row only surfaces if someone looks; production would add a scheduled check with a Slack alert.

Scalability

Each call is stateless, so the call logic scales to 100+ clients unchanged. The Google Sheet doesn't: with no transactions, concurrent bookings can race past the duplicate check, and the API quota runs out before 100 daily calls, both pointing to a real database. Nothing marks a booking completed after service either, so that needs a closing step. Volume also needs VAPI's concurrency cap raised and the batch paced.

Skills Demonstrated

The technical capabilities this build required, at a glance.

Voice AI Prompt DesignTool / Function-Calling ArchitectureWebhook Contract Design & DebuggingWorkflow Orchestration (n8n)Outbound Campaign LogicCall Data LoggingHuman-in-the-Loop Escalation DesignCross-Platform API TroubleshootingTelephony Reliability Engineering

Why I Built It This Way

This build leans into what happens when you wire two platforms together, instead of showing only a clean happy path: a strict response contract that fails silently if you get it wrong, an API that didn't behave the way its own documentation described, and a handful of other real bugs, several of the kind that fail quietly rather than with a clear error. I only found them by placing real calls and inspecting real n8n executions, not by reading either platform's docs. A voice AI build that only demonstrates the happy path doesn't show much about how someone works through a real integration. This one shows what that work looks like, including the parts that didn't work on the first try.

Appendix

Supporting reference material for this build.

Sample Assistant Prompt Excerpt

Grounding lookups in real data
Get the caller's name and phone number early in the call, then call
lookup_customer with that phone number before saying anything about their
vehicle or service history. Never guess or make up vehicle details, recall
status, or service history — only state what lookup_customer returns.
Surfacing recalls proactively
If lookup_customer shows an open recall on the vehicle, mention it
proactively and offer to schedule the recall repair — don't wait for the
caller to bring it up.

Screenshots

Got a process like this?

If any of this sounds like a bottleneck you're living with, tell me about it. I'll be straight about whether automation is worth it.