Module 6: Real Channels: Web Chat, WhatsApp, Telegram, and Voice

5. Voice agents: Vapi, Retell, and ElevenLabs

Description

By the end of this lesson you'll be able to explain what's really inside a voice agent — the recognition, model, and synthesis stack, and the latency budget that governs it — choose wisely between the two possible architectures for connecting it to n8n, and register your workflows as tools for a voice agent on the three platforms that dominate the market: Vapi, Retell, and ElevenLabs. You'll also know how to write a prompt for voice, which doesn't look like a chat prompt, and you'll have the honest map of what a minute of spoken conversation costs.

This matters for a fairly concrete market reason. When you look through job postings asking for AI agent builders, voice shows up in around 28% of them — more than RAG, which is the topic everyone teaches. And almost no Spanish-language material covers it: it gets mentioned on one slide, someone says "voice can also be done" and moves on. That combination — high demand, almost no training supply — is exactly where it's worth investing some time. It's also the channel that forces you to rethink the most of what you've been carrying: with no screen, no buttons, no way to re-read, and a clock running, almost none of your previous design decisions survive intact.

Connection to the module: you're coming from three text channels that look a lot alike — a trigger that receives, a brain that reasons, a node that responds. Voice breaks that mold, and that's why it's at the end of the channel block. Here, in the architecture you're going to use, n8n stops being the brain and becomes the hand: the voice platform drives the conversation and your workflows are the tools it checks. It's a conceptual shift, not a connection detail, and understanding it well is what separates a voice demo that works from one that falls apart on the second sentence. Everything you learned in Module 4 about tool contracts becomes, here, the central piece.

The waiter reciting the menu

Think of the difference between reading a menu and having a waiter recite today's specials to you.

With the menu in hand you can go through it in whatever order you want, go back, compare two dishes, put it down for a moment to take a call and pick back up exactly where you were. All the information is available at once and you control the pace.

When the waiter recites, none of that exists. The information arrives in an order you didn't choose, at a speed you don't control, and disappears the moment it passes. If they recite twelve dishes, you remember two: the first and the last. If they say "the price is two hundred eighty-seven fifty," you have to hold that number in your head while they keep talking. And if you get distracted for three seconds, there's no way to rewind: only to ask them to repeat it, which people avoid doing because it feels awkward.

That's why good waiters don't recite twelve dishes. They say three, grouped, and ask. "Today we have three fish dishes, two meats, and a pasta. Where should I start?" They reduce it so it fits in the listener's working memory, and they hand control back with a question.

A voice agent's entire design comes from that. It isn't a chatbot with a speaker bolted on: it's a conversation with different rules, where information has to fit inside the head of someone who can't go back. When lesson 1's order #4521 response broke on being carried over to voice, it didn't break because of formatting — it broke because it had too much information for a channel where information disappears.

What's inside a voice agent

Before connecting anything, let's look at the machine. A voice agent over the phone is a chain of three pieces plus a conductor.

        The person speaks
              │
              ▼
    ┌─────────────────────┐
    │  ASR                │  Automatic Speech Recognition.
    │  voice → text        │  Converts the audio into words.
    └──────────┬──────────┘
               │  "hi I want to know about my order four five two one"
               ▼
    ┌─────────────────────┐
    │  LLM                │  The language model. Decides what to
    │  text → text        │  respond and when to call a tool.
    └──────────┬──────────┘  ◄── THIS is where n8n comes in as a TOOL
               │  "Let me check that. Your order's on the way…"
               ▼
    ┌─────────────────────┐
    │  TTS                │  Text To Speech. Converts the text into
    │  text → voice        │  audio with a synthetic voice.
    └──────────┬──────────┘
               │
               ▼
        The person listens

    And above all three, the CONDUCTOR (turn-taking):
      · VAD — detects when there's voice and when there's silence
      · endpointing — decides when the person FINISHED speaking
      · barge-in — lets the person interrupt the agent

You already know the chain's three pieces conceptually. The conductor is what makes voice hard, and it's what the platforms sell.

Endpointing is the most underrated problem. When someone says "my order is… four five two one", there's a pause in the middle. Did they finish talking or are they thinking? If the system decides too quickly they finished, it interrupts the person mid-sentence, which feels very unpleasant. If it waits too long, the conversation feels slow and dead. That tuning — typically a few hundred milliseconds of silence — is one of the things that most tells a good voice agent apart from a bad one, and it has nothing to do with the model's quality.

Barge-in is being able to interrupt the agent. If the agent starts reciting and the person says "no, wait," an agent with no barge-in keeps talking over them. With barge-in, it goes quiet and listens. It's the difference between talking with something and something talking at you.

The latency budget

Here's the number that governs voice's whole design. In a normal human conversation, the silence between one person finishing and the other starting is about 200 milliseconds. A silence longer than a second reads as hesitation; longer than two, as the call having dropped.

Now add up the chain:

Approximate budget for a voice response

  endpointing (waiting to confirm they finished)   ~200-500 ms
  ASR (transcribing what was said)                 ~100-300 ms
  LLM (reasoning and generating the response)       ~300-1500 ms   ◄── the big one
  TTS (starting to produce audio)                   ~100-300 ms
  network (round trip)                              ~50-150 ms
  ─────────────────────────────────────────────────────────
  total perceived before hearing the first syllable ~750-2750 ms

Notice where the margin is and where it isn't. ASR and TTS are fairly fixed. Endpointing gets tuned but has a floor. The only component where your decisions move hundreds of milliseconds is the LLM, and above all, the tools it calls.

And that's the consequence that makes this lesson make sense within this guide. If your n8n workflow is a tool for that voice agent and it takes four seconds to respond, the person on the phone hears four seconds of silence in the middle of a sentence. There's no "typing" indicator, no three dots, nothing. Four seconds of nothing.

That has three solutions and all three get used:

  1. Make the tool fast. A workflow that queries one row of a database and returns three fields can respond in under a second. A workflow that calls an AI agent that in turn delegates to two specialists, can't. This is the main reason Module 5's multi-agent system does not connect directly as a voice agent's tool.
  2. Have the agent talk while it waits. All three platforms have an option for the agent to say something — "let me check that for a second" — when starting the tool call. Retell literally calls it Speak During Execution. It's the voice version of Telegram's "typing…"
  3. Make the tool asynchronous. The agent triggers the action, keeps conversing, and the result gets processed afterward. Useful for actions that don't condition the next sentence: creating a ticket, sending an email, logging something.

The two architectures

There are two ways to combine n8n with a voice platform, and picking the wrong one is this lesson's structural mistake.

Architecture A — the platform is the brain, n8n is the hand

   Phone
      │
      ▼
  ┌────────────────────────────────────────────┐
  │  Vapi / Retell / ElevenLabs                │
  │    ASR + LLM + TTS + turn-taking           │
  │    the agent's system prompt                │
  │    ▼                                       │
  │    registered tools ───────────┐           │
  └─────────────────────────────────┼──────────┘
                                    │  HTTP
                                    ▼
                          ┌──────────────────────┐
                          │  n8n                 │
                          │  Webhook → logic →   │
                          │  Respond to Webhook  │
                          │                      │
                          │  lookup_order        │
                          │  lookup_charge       │
                          │  create_ticket       │
                          └──────────────────────┘

The voice agent lives on the platform. Its prompt is there, its model is there, and the conversation cycle is handled there. n8n only shows up when the agent needs to check or do something: every workflow is a tool, exposed as a webhook.

This is the architecture used in practice, and this is the one this lesson teaches. The reason is the latency budget you just saw: a spoken conversation's turn-taking demands coordination between ASR, LLM, and TTS that happens in milliseconds and with streaming audio. n8n isn't built for that and shouldn't be — it's a workflow orchestrator, not a real-time engine.

Architecture B — n8n is the brain, the platform only transports

   Phone ─► platform (ASR + TTS) ─► n8n webhook ─► AI Agent ─► responds with text
                                                                 ─► platform reads it

Here the platform only converts speech to text and text to speech, and all the reasoning lives in your n8n AI Agent. It's tempting: it keeps a single brain for every channel, which is exactly what lesson 7 is going to preach.

And on voice, it's almost always the wrong decision. Every conversation turn turns into a complete round trip to your n8n instance, with the workflow starting up, the call to the model from n8n, and the way back. That easily adds up to one or two seconds per turn, on top of everything else. The result is an agent that answers correctly and feels unbearable.

There's one case where B does make sense: asynchronous voice conversations, where there's no real-time turn. A WhatsApp voice message that gets transcribed, processed by the agent, and answered with text or a generated audio — there's nobody waiting on the line there, and three seconds don't matter. That case is a natural extension of lesson 3, not a phone voice agent.

The practical rule: if there's a person waiting on the line, architecture A. If the audio is a message processed as it arrives, architecture B.

Vapi: registering an n8n workflow as a tool

Let's look at the concrete mechanics with Vapi, which is the platform with the cleanest integration model for this case.

On Vapi you define an agent (an assistant) with its prompt, its voice, and its model. And you register tools for it. To call one of your endpoints, the tool type is Function, and it's configured with:

  • Name — the tool's identifier, in English and with no spaces: lookup_order.
  • Description — what it's for. This is what the model reads to decide whether to call it. It's exactly the same criterion as Module 4: a vague description produces wrong calls.
  • Server URL — your n8n workflow's production webhook URL.
  • Parameters — the JSON schema of the arguments the model must extract from the conversation.
  • Async Mode and Timeout — whether the agent waits for the result, and how long.

When the agent decides to use the tool, Vapi makes a POST to your URL with a body shaped like this:

{
  "message": {
    "type": "tool-calls",
    "toolCallList": [
      {
        "id": "toolu_01DTPAzUm5Gk3zxrpJ969oMF",
        "name": "lookup_order",
        "arguments": { "order_id": "4521" }
      }
    ]
  }
}

And it expects back exactly this shape:

{
  "results": [
    {
      "toolCallId": "toolu_01DTPAzUm5Gk3zxrpJ969oMF",
      "result": "Order 4521 is in transit, estimated delivery July 23rd."
    }
  ]
}

Two details that break half of first integrations:

The response's toolCallId has to be the same id from the request. It's the mechanism Vapi uses to match your response with the right call, because there can be several in flight. If you omit it or make one up, the agent gets nothing and stays silent.

The result can be text or an object, and for voice text is almost always the right call. If you return {"status":"in_transit","eta":"2026-07-23"}, the model is going to have to translate that into a spoken sentence, and sometimes it gets it wrong — reading the date in calendar format, for instance. If you return a phrase already drafted to be spoken, the result is much more consistent. In voice, the drafting work is better done in the tool, not left to the model.

Worked example: TuTienda's lookup_order, spoken

Let's build the complete tool. In n8n, a three-node workflow:

Webhook (POST)  →  Postgres / Sheets  →  Respond to Webhook

Step 1 — The webhook.

# Node: Webhook — Name: voice_lookup_order
HTTP Method:   POST
Path:          voice/lookup-order
Respond:       Using 'Respond to Webhook' node
Authentication: Header Auth  ← a shared secret; see the note below

About authentication: this endpoint is going to be open to the internet. Vapi lets you configure custom headers on the tool, so the reasonable minimum is a header with a secret n8n verifies. It's not serious cryptography, but it stops anyone who finds the URL from checking your orders. The rigorous version — signatures and validation — is Module 7's material.

Step 2 — Extract the argument. The order_id comes buried inside Vapi's structure:

# Node: Set — Name: extract_args
# The path to the arguments depends on Vapi's API version.
# Confirm with a real execution before treating it as final.

tool_call_id = {{ $json.body.message.toolCallList[0].id }}
order_id     = {{ $json.body.message.toolCallList[0].arguments.order_id }}

Step 3 — Query. The same Sheets or Postgres node you used as a tool in Module 5. No changes.

Step 4 — Respond in Vapi's format, with the text already drafted for voice:

# Node: Respond to Webhook — Name: respond_to_vapi
# Respond With: JSON

{
  "results": [
    {
      "toolCallId": "{{ $('extract_args').item.json.tool_call_id }}",
      "result": "{{ $json.order_status === 'in_transit'
                    ? 'The order is on the way and arrives approximately on ' + $json.eta_spoken
                    : 'The order is ' + $json.status_spoken }}"
    }
  ]
}

Notice eta_spoken and status_spoken. The idea is to prepare, in the query itself, a version of each piece of data ready to be spoken out loud: not 2026-07-23 but Thursday, July 23rd; not in_transit but on the way. That preparation is the adapter's job, and doing it here saves the model from improvising and sometimes getting it wrong.

Step 5 — Register the tool in Vapi:

# Vapi > Assistant > Tools > Add Tool > Function

Name:        lookup_order
Description: Checks the status and estimated delivery date of a
             TuTienda order given its number. Always use it when
             the customer asks about an order. Doesn't work for
             charges or returns.
Server URL:  https://YOUR-N8N-INSTANCE/webhook/voice/lookup-order
Parameters:
  order_id (string, required) — The order number the customer said,
    digits only. If the customer didn't give it, ask for it before
    calling this tool; never make it up.
Async:   No  (the agent needs the result to keep talking)
Timeout: 10 seconds

What to expect. You call the agent, say "I want to know about my order four five two one," and the agent responds with something like "Let me check that… Your order is on the way and arrives approximately Thursday, July 23rd." In n8n you're going to see an execution of the workflow with Vapi's whole payload. If the agent stays silent, the two suspects are a mis-copied toolCallId and a timeout that's too short.

Notice something important about the tool's description: it's identical in spirit to Module 4's — what it does, when to use it, when not to — but it includes a voice instruction that wasn't needed in chat: "if the customer didn't give it, ask for it before calling." In chat, an agent calling with a missing piece of data produces a recoverable error. In voice, it produces silence and then a confusing sentence, in the middle of a call. It's worth being more explicit.

Retell: the same idea with different names

Retell calls the same thing a custom function. Its configuration includes these fields:

FieldWhat it does
NameUnderscore-separated identifier: lookup_order
DescriptionWhat the model reads to decide whether to use it
HTTP MethodGET, POST, PATCH, PUT, or DELETE
URLYour n8n webhook
HeadersStatic or dynamic headers — your shared secret goes here
Query ParametersGet appended to the URL
ParametersThe JSON schema for the arguments
Response VariablesExtracts values from the response to use later in the call
Speak During ExecutionWhether the agent says something while the function runs
Speak After ExecutionWhether the agent keeps talking once it's done, without waiting for the user
TimeoutTwo minutes by default

When Retell calls your URL, the body carries three things: name (the function's name), args (the arguments), and call (the call object, which includes the in-progress transcript). It also sends an X-Retell-Signature header used to verify the request genuinely comes from Retell. There's an "args only" option that flattens the body and leaves the arguments at the top level, which simplifies expressions quite a bit.

The response it expects is simpler than Vapi's: any code between 200 and 299, with the body as text, a JSON object, or binary, with a cap of about 15,000 characters. There's no identifier matching needed.

Two fields from that table deserve a comment because they have no obvious equivalent in chat.

Speak During Execution is voice's "typing…" With it on, the agent says something while it waits — "one moment, let me check" — and the silence stops existing. The general recommendation is to turn it on almost always, except for instant functions or tasks where the agent isn't expected to keep talking.

Response Variables is more interesting than it sounds. It lets you extract a value from your workflow's response and store it as a call variable, available for the rest of the conversation. If your tool returns the customer's name, the agent can use it twenty turns later without checking again. It's platform-provided session memory, and it saves calls.

Retell also publishes a community node for n8n (@retellai/n8n-nodes-retellai), installed from Settings → Community Nodes, useful for the opposite direction: triggering outbound calls from a workflow. It's useful when your business flow is "when X happens, have the agent call this customer." Keep in mind a community node isn't an official one: it installs separately and its maintenance depends on whoever publishes it.

ElevenLabs: webhook tools and dynamic variables

ElevenLabs, known above all for the quality of its synthetic voices, also has a conversational agent platform. Its mechanism for calling an external endpoint is webhook tools (or server tools), and its configuration looks a lot like defining a complete HTTP request:

  • Name and Description — same as the other two.
  • Method and URL — the verb and the endpoint.
  • Path parameters — variables inside the URL, in curly braces: /orders/{order_id}.
  • Query parameters — the ones that go after the question mark.
  • Body parameters — the body, as JSON or as encoded form data.
  • Authentication and headers — supports OAuth2, Bearer tokens, basic auth, and custom headers.

And a distinction of its own worth understanding, because it's elegant: every parameter has a value type, with two options.

  • LLM Prompt — the model extracts the value from the conversation. It's what you already know: the customer said an order number, the model puts it in the parameter.
  • Dynamic Variable — the value comes from a call variable, not from what the model understood. And those variables can get updated with other tools' responses.

That second option solves a real problem. Imagine that when the call starts, your system already knows who's calling, because it identified the incoming number. That customer_id shouldn't come from what the model understands over the phone: it should be fixed from the first second and travel on every tool call. With a dynamic variable, the customer_id is a system fact, not a model interpretation. In voice, where recognition can mix up a digit, every piece of data you can pull out of the interpretation's scope is one less error.

Think about it for a moment: how many of the data points your agent handles today genuinely need to come from what the person says, and how many could you know in advance? In a support agent with caller ID, the answer usually surprises.

The connection to n8n is exactly the usual one: a Webhook node receiving, the logic in the middle, and a Respond to Webhook returning. On ElevenLabs' side, verify in the current documentation what a webhook tool's timeout limit is, because not every platform publishes it with equal clarity and it's a fact worth knowing before connecting a slow workflow.

The three, compared

VapiRetellElevenLabs
Mechanism's nameFunction toolCustom functionWebhook (server) tool
Response shaperesults with matched toolCallIdAny 2xx, up to ~15,000 charactersNormal HTTP response
"Talk while waiting"Yes (assistant option)Yes, Speak During ExecutionYes, depending on agent configuration
Async modeYes, explicitDepending on configurationDepending on configuration
Origin verificationConfigurable headersX-Retell-SignatureHeaders and auth schemes
n8n nodeNot official; use WebhookCommunity node for outbound callsNot official; use Webhook
Strong atFlexibility and fine controlTelephony and call flowsVoice quality and naturalness

What's worth taking away from that table isn't which one is better — all three work and the choice usually depends on price and voice quality in your language — but that the pattern is identical across all three: you define a tool with a name and a description, point it at an n8n webhook, declare the parameters, and return a result. If you learn one, the other two are a matter of reading what they named each field.

None of the three has, at the time of writing, a native n8n node for the agent's side. The bridge is the Webhook node, which is generic and stable, and that's actually an advantage: your integration doesn't depend on someone maintaining a specific node.

A voice prompt is a different prompt

Here's the part most underestimated. A system prompt written for chat, dropped as-is into a voice agent, produces an unbearable agent. These are the differences that pay off the most.

Length: two or three sentences, maximum. In chat, a paragraph-long response reads in five seconds and can be skimmed. In voice, that same paragraph is twenty seconds during which the person can't do anything and has probably already lost the thread. The instruction has to be explicit and quantitative: "respond in two or three sentences; if you need to give more information, deliver it in parts and ask before continuing."

No lists. A five-option list in voice is a list nobody remembers. Three at most, and preferably grouped: "I can help with orders, charges, or returns. Which of the three?"

No formatting at all. No asterisks, no bullets, no links. This isn't optional: the synthesis engine is going to either read them or handle them unpredictably.

Numbers and dates, the way a person would say them. 2026-07-23 is "Thursday, July 23rd." $1,200.00 is "twelve hundred dollars." A four-digit order number is best said digit by digit and repeated to confirm. It's worth resolving this in the data the tool returns, and also instructing it in the prompt as a safety net.

Explicit confirmation before any action. In chat, if the agent misunderstood, the person reads it and corrects it. In voice, recognition can mix up a digit and nobody finds out until the action has already run. The rule is: repeat the critical piece of data and wait for confirmation. "So I'm cancelling order four-five-two-one, is that right?"

A plan for when it doesn't understand. It's going to happen: noise, an accent, a half-said word. The prompt has to say what to do, and especially what to do the second and third time. Asking twice is fine; by the third, the right answer is usually offering another route — "I'll text you so you can write the details" — or transferring to a person. An agent that asks the same thing four times is worse than having no agent.

A voice system prompt for TuTienda, compared to its chat version:

# Voice system prompt — TuTienda (for the platform's assistant)

You are TuTienda's phone assistant. You're talking over the
phone, so the person can't read anything or go back.

How you talk:
- Two or three sentences per turn. Never more.
- No lists longer than three items.
- No asterisks, bullets, links, or web addresses.
- Dates the way a person would say them: "Thursday, July
  twenty-third," not "twenty-three oh seven."
- Order numbers, digit by digit.

Before any action:
- Repeat the key piece of data and ask for explicit confirmation.
- Never execute a cancellation, a change, or a refund without
  having received a clear "yes."

If you don't understand:
- Ask them to repeat, once.
- If you still don't understand, ask for the data another way (for
  example, digit by digit).
- On the third try, offer to send a text message or transfer to a
  team member. Don't push further.

Never say web addresses out loud. If there's a link to share,
offer to send it by message.

Compare it to Module 5's triage_agent prompt. The business content is the same; what changed is entirely the conversation's shape. That prompt lives on the voice platform, not in n8n, and it's the best possible example of why the channel layer exists.

The post-call webhook: this is where n8n shines again

Up to now n8n was the agent's hand during the call. There's a second moment where n8n is even more useful: when the call ends.

All three platforms send, on hangup, a webhook with a summary of what happened: the full transcript, a generated summary, the duration, the outcome, and often structured variables the agent picked up during the conversation.

That webhook arrives at n8n like any other, and there you can deploy everything you know how to do:

Webhook (voice platform's post-call)
   │
   ├─► Set: extract transcript, summary, duration, number
   │
   ├─► AI Agent or Basic LLM Chain: classify the outcome
   │      (resolved / needs follow-up / upset customer)
   │
   ├─► IF: needs follow-up?
   │      ├── yes → create_ticket + notify the team
   │      └── no  → just log it
   │
   ├─► Postgres: save the conversation in the customer's history
   │
   └─► WhatsApp → Send Template: send the customer the tracking
         link that couldn't be given over the phone

That last node closes a nice loop in the module. The URL that was impossible to convey in voice gets sent over WhatsApp, in the same flow, to the same customer. Channels stop being silos and become a system.

And notice the second-to-last branch: saving the transcript in the same memory store the other channels use means a customer who called on Monday and writes over WhatsApp on Tuesday gets recognized. That's exactly lesson 7's promise, and here you already see it working.

What it costs, said plainly

Voice is the module's most expensive channel and it's worth knowing that before building a thirty-minute demo.

The billing model is per minute of conversation. Some platforms bundle everything — recognition, model, synthesis, telephony — into one per-minute rate; others charge each component separately and you have to add them up. Either way, the clock runs while someone's talking, and the typical order of magnitude is in the range of a few cents of a dollar per minute. A three-minute call costs little; ten thousand three-minute calls, not so much.

The phone number gets paid for separately. If you want people to be able to dial a number, you have to buy or port one, with its monthly rent. That's independent of what you consume.

What you control is duration. Here engineering and the bill line up for once: an agent that gets to the point, confirms correctly the first time, and doesn't repeat questions, costs less. An agent that doesn't understand and asks four times stretches the call and makes every conversation more expensive. Optimizing the experience and optimizing the cost are, in voice, the same task.

The cheap route for learning, which is what this lesson recommends:

  1. Start with no phone number. All three platforms have a way to test the agent from the browser, talking through the computer's microphone. It skips the whole telephony part and its cost, and for learning how to build the tool and write the prompt it's exactly as useful.
  2. Use the trial credits the platforms give on signup, and check the consumption after each work session to calibrate how far it lasts you.
  3. Test the tool first with an HTTP tool. Before connecting anything to the voice platform, send your n8n webhook the exact payload the platform would send, from any HTTP client, and confirm the response has the right shape. That rules out half the problems without spending a minute of conversation.
  4. A single, shared number, if you end up needing telephony. Don't buy one per experiment.

Common mistakes

Connecting Module 5's multi-agent system as a voice tool (conceptual). What happens: someone exposes their complete triage_agent — with its two specialists and its loops — as a Vapi tool, because it's the agent that already works. The test call produces between eight and fifteen seconds of absolute silence in the middle of the conversation, and sometimes a timed-out wait. Why it happens: a multi-agent system makes several calls to the model in series, and that's correct in chat and catastrophic in voice. How to spot it: measure how long your workflow takes to respond by running it with fixed data; if it's over two seconds, it isn't a voice tool. How to fix it: expose the domain toolslookup_order, lookup_charge — directly as the voice agent's functions, and let the platform's model do the reasoning. The multi-agent system is for text channels, where time can be disguised with a "typing…"

Returning raw JSON and expecting it to sound good (practical). What happens: the tool returns {"status":"in_transit","eta":"2026-07-23"} and the agent says things like "the status is in transit and the e-t-a is two thousand twenty-six dash zero seven dash twenty-three." Why it happens: the model has to translate data into speech, and sometimes it does and sometimes it doesn't, especially with technical values and ISO-formatted dates. How to spot it: it's audible immediately on the first test call. How to fix it: return text already drafted to be spoken, prepared in the workflow. It's the adapter's job and it makes the response consistent across every run, not just the ones where the model got lucky.

Forgetting the toolCallId in the response to Vapi (practical). What happens: the n8n workflow runs perfectly, the successful execution shows in the log, and the voice agent goes silent or says it couldn't get the information. Why it happens: Vapi matches responses to calls by that identifier; without it, your response corresponds to nothing and gets discarded. How to spot it: a successful execution in n8n and silence on the call is the exact signature of this error. How to fix it: save the call's id at the start of the workflow and return it identical in the toolCallId field. And confirm the path to it against a real payload, because Vapi's nested structure is easy to get wrong.

Not turning on "talk while waiting" (practical). What happens: the tool takes two seconds, which is a perfectly reasonable time, and the person hears two seconds of total silence in the middle of a sentence. A lot of people say "hello?" or hang up. Why it happens: in chat, two seconds are invisible; in voice, they're long, and the option that solves it is off by default in some configurations. How to spot it: call yourself and listen to yourself waiting. How to fix it: turn on the corresponding option (Speak During Execution on Retell, the equivalent on the others) and write a short, natural waiting phrase. And don't put in a long phrase: if the tool responds in 800 milliseconds, a three-second waiting phrase makes the experience worse instead of better.

Leaving the tool's webhook open with no verification at all (practical). What happens: the lookup_order endpoint stays accessible on the internet with no authentication, and anyone who finds the URL can check any order's status by trying numbers. Why it happens: during development you remove authentication so it works on the first try and forget to put it back. How to spot it: call your webhook from a terminal with no header at all; if it responds with data, it's open. How to fix it: at minimum, a header with a shared secret configured on the voice platform and verified in n8n; and on Retell, additionally, verifying the X-Retell-Signature. The serious treatment of trust boundaries is Module 7, but an endpoint with zero verification shouldn't stay alive that long.

Exercises

Exercise 1 — Translate a tool to voice. Take Module 5's check_return_eligibility tool, which given an order returns whether the return is eligible and until what date. Write: (a) its Description for a voice agent, including the instruction about missing data; (b) the three possible outcomes as text ready to be spoken, instead of as JSON; (c) what the agent should do if recognition didn't correctly capture the order number.

See solution

(a) The description:

Name: check_return_eligibility
Description:
  Determines whether a TuTienda order is still within its return
  window and until what date. Use it when the customer says
  they want to return, exchange, or send back a product.
  Doesn't work for charges or shipment status checks.
  Requires the order number. If the customer didn't give it or
  you didn't understand it clearly, ask for it digit by digit and
  confirm it before calling this tool. Never make it up or
  infer it from an earlier conversation.

(b) The three outcomes, drafted for voice:

Eligible:
  "Yes, you can still return it. You have until Sunday, August
   third."

Not eligible, past deadline:
  "The window to return that product has already passed. It was
   fourteen days because it's an electronic item."

Not applicable (category with no returns):
  "That product doesn't accept returns because of its category.
   I can connect you with a team member if you'd like to look into
   it."

Notice three decisions. None of them mentions the order number, because the customer just said it and repeating it is noise. Dates are said the way a person would say them, with the day of the week included, which is what people actually use to orient themselves. And the third case — the hard bad news — offers a way out in the same sentence, instead of leaving the person with a dry "no" and silence.

(c) If it didn't capture the number correctly. Ask digit by digit and confirm before checking: "Could you confirm the order number digit by digit, please?" and then "So it's four, five, two, one, correct?" And there's a second part that usually gets forgotten: if after two tries it's still unclear, the right move isn't a third try but switching routes — offering to send a text message where the customer types the number, or transferring. Insisting is where a call turns into a bad experience and, on top of that, a more expensive one.

Why it works: the exercise shows that bringing a tool to voice isn't changing its output format — it's rewriting its whole contract, including what to do when the channel fails, a case that practically doesn't exist in chat.

Exercise 2 — Choose the architecture. For each of these four TuTienda cases, say whether you'd use architecture A (platform as brain, n8n as tool) or B (n8n as brain), and justify it in one line.

  1. A number customers call to check their order's status.
  2. A flow processing the voice messages customers send over WhatsApp.
  3. An agent calling customers with abandoned carts to offer help.
  4. A voicemail box that transcribes the message and creates a ticket with the request.
See solution
  1. Architecture A. There's a person waiting on the line, with real-time turns. The latency budget rules.
  2. Architecture B. Nobody's waiting: the audio already arrived and gets processed whenever it's its turn. You transcribe it, hand it to the usual triage_agent, and respond over WhatsApp. Here it does make sense for the brain to be the same one the other channels use.
  3. Architecture A for the conversation, plus n8n at both ends. n8n triggers the outbound call — here's where Retell's community node or an API call makes sense — the platform drives the conversation with its tools, and on hangup the end-of-call webhook goes back to n8n to log the outcome and decide the follow-up. It's the case that best shows the two architectures don't compete: n8n is the business process's orchestrator and the platform is the conversation.
  4. Neither one, strictly speaking: a voice agent isn't needed. It's a transcription plus an extraction, which is the procedural AI type covered in the ecosystem's earlier guide. A voicemail box doesn't converse. This case is in the exercise on purpose, because the temptation to solve everything that sounds like audio with a voice agent is real, and a conversational agent where there's no conversation is complexity and cost for nothing.

Why it works: the question that resolves all four is the same — is there someone waiting for a real-time response? — and the fourth adds the prior question that sometimes gets skipped: is there even a conversation?

Exercise 3 — Build and measure. Set up the lookup_order tool as a webhook in n8n following the worked example, but don't connect it to any voice platform yet. Instead: (a) send your webhook, with an HTTP client, the exact payload Vapi would send, and confirm the response has the right shape with the matched toolCallId; (b) measure how long the workflow takes end to end, running it five times; (c) say whether that number is acceptable for voice and what you'd do if it weren't.

See solution

(a) The test payload, with a made-up but consistent identifier:

{
  "message": {
    "type": "tool-calls",
    "toolCallList": [
      { "id": "test_001", "name": "lookup_order",
        "arguments": { "order_id": "4521" } }
    ]
  }
}

And what you have to see back, with an identical test_001:

{ "results": [ { "toolCallId": "test_001",
                 "result": "The order is on the way and arrives approximately Thursday, July twenty-third." } ] }

If the toolCallId isn't exactly test_001, you already found the lesson's most common error without spending a minute of call time. And if the result contains an ISO-formatted date or a value like in_transit, you found the second one.

(b) Five runs because the first one is almost always slower — the database connection, cold caches — and a single measurement is misleading. Note the worst case, not the average: in voice, the worst case is what the person hears.

(c) The criterion: under a second is comfortable; between one and two is acceptable with a waiting phrase turned on; over two seconds needs fixing before connecting it.

If it doesn't measure up, in this order: remove from the workflow any node that isn't essential for responding — notifications, logs, email sends — and move them to a separate flow that runs afterward; use a direct database query instead of a spreadsheet, which is noticeably slower; and verify there's no call to a language model inside the tool, which is the most frequent cause of a slow workflow and the easiest one to overlook, because it doesn't show in chat.

Why it works: testing the tool in isolation with an HTTP client before connecting it is the technique that saves the most time and money in voice, and it's exactly the same principle from Module 5 — testing each specialist alone before connecting the orchestrator — applied to a new channel.

Summary and next step

You now know what's inside a voice agent: a chain of recognition, model, and synthesis, governed by a turn conductor that decides when someone finished speaking and whether they can interrupt. And you know which number's in charge: the latency budget, where the only component your decisions genuinely move is the tools the agent calls. From there comes the right architecture — the platform drives, n8n is the hand — and from there also comes why Module 5's multi-agent system doesn't connect directly to a phone call.

You learned the concrete pattern across all three platforms: a function with a name and a description, an n8n webhook as the destination, declared parameters, and a returned result — with each one's particulars, Vapi's matched toolCallId, Retell's signature and response variables, and ElevenLabs' value types that let you pull a piece of data out of the model's interpretation scope. You wrote a voice prompt, which is a different prompt: two sentences, no lists, no formatting, numbers the way a person would say them, confirmation before acting, and an explicit plan for when it doesn't understand. And you saw n8n come back to the center when the call ends, with the end-of-call webhook that logs, classifies, creates the ticket, and sends over WhatsApp the link that was impossible to give over the phone.

Before moving on you should be able to: explain in one sentence why architecture A dominates in telephony and in which concrete case B is right; name the three ways to keep a two-second silence from being noticed on a call; and say why a voice tool should return drafted text instead of JSON.

Lesson 6 takes what you just saw in voice — that the same information gets said differently depending on the channel — and turns it into a method for all four. Length, format, buttons, asynchrony, and each channel's pacing, with the same agent response written four times and a clear rule about where that adaptation lives: in the adapter, never in the brain.

Resources

  • Custom tools — Vapi Docs — a function tool's configuration, the toolCallList payload's exact shape, and the results response with toolCallId.
  • Custom functions — Retell AI Docs — every field of a custom function, including Speak During Execution, Response Variables, and the response's character cap.
  • Retell AI + n8n — the @retellai/n8n-nodes-retellai community node for triggering outbound calls from a workflow.
  • ElevenLabs Agents — tools — webhook tools, their parameter types, and the distinction between a model-prompted value and a dynamic variable.
  • Webhook node — n8n Docs — the node that's the real bridge with all three platforms, with its header-authentication options.
  • Respond to Webhook node — n8n Docs — how to return exactly the JSON each platform expects, which is half of this lesson.