Module 6: Real Channels: Web Chat, WhatsApp, Telegram, and Voice
3. WhatsApp Business API: the agent where the customer is
Description
By the end of this lesson you'll be able to connect Module 5's agent system to WhatsApp: tell the API apart from the desktop app it shares a name with, put together the pieces Meta requires (developer account, business portfolio, app, WhatsApp Business account, and number), create the two distinct credentials n8n needs — one for receiving and one for sending — configure the WhatsApp Trigger, and respond with the WhatsApp Business Cloud node. And, above all, you'll understand the rule that changes the whole conversation's design: the 24-hour window, with its corresponding bill.
This matters because WhatsApp is the number-one customer support channel in LATAM, and not by a little. An agent serving over WhatsApp reaches people who will never open your website's chat, because they already have the app open all day. It's also the channel where a poorly designed agent does the most damage: it's the same place where the person receives messages from their family, and a robotic response there feels worse than anywhere else. And it's, by far, the channel with the most moving parts outside your control.
Connection to the module: you're coming from lesson 2, where you opened a channel n8n controls end to end. Here everything structural is the same — a trigger that receives, a brain that reasons, a node that responds — but the channel layer now belongs to someone else, and that someone else is Meta. You're going to meet the same decisions again (session identity, response mode, format) solved a different way, and you're going to find two new rules that didn't exist in the web chat: the service window and templates. Lesson 4 uses Telegram as the cheap lab to practice these ideas without paperwork; if your Meta business verification is still pending, you can read this whole lesson, skip to lesson 4, and come back once it goes through.
The owner's cell phone and the company's line
Think of a small business handling WhatsApp from the owner's cell phone. It works: customers write, someone answers, things get resolved. And it has the obvious limits of that solution: only one person answers, from one device, when they're holding it. If the owner goes on a trip, support goes with them. There's no way for a system to answer there, because the phone is a phone.
Now think of a big company's phone line. Nobody carries that device in their pocket. The line reaches a central system, the central system routes it, there are recordings, there are menus, every call gets logged, and several systems can be listening to that same line at once. It's more powerful and also more bureaucratic: setting it up takes days, there's a contract involved, and someone has to verify the company exists.
WhatsApp has exactly those two forms, and confusing them is this lesson's costliest mistake.
The WhatsApp Business app is the owner's cell phone. It's downloaded for free, installs on a phone, has a catalog and quick replies, and has no way to connect to n8n at all. Nothing you learn in this lesson works with it.
The WhatsApp Business Platform, with its Cloud API, is the company's line. It doesn't live on any phone: it lives on Meta's servers. It sends incoming messages to a webhook you point it at, and it accepts outgoing messages via an authenticated HTTP request. That's what n8n uses, and it's the only one useful for an agent.
The confusion happens because the names look alike, because both come up if you search "WhatsApp Business," and because the app is free and the API isn't. If at any point in this lesson you find yourself installing something on a phone, stop: you went down the wrong path.
The pieces Meta is going to ask for
This is the part of the lesson that most resembles paperwork, and there's no way to skip it. It's worth seeing it as a map before starting, because Meta's names shift position in the panel frequently and it's easy to get lost looking for a button.
Meta developer account
│ (your identity as a builder; it's free)
│
└── Business Portfolio
│ (TuTienda's representation as a company;
│ it's what eventually needs to be VERIFIED)
│
├── Meta App
│ │ (the "program" you add the WhatsApp
│ │ product to; the App ID and App Secret come from here)
│ │
│ └── Webhook ──────────────► your n8n URL
│ (one single webhook per app — remember it)
│
└── WhatsApp Business Account (WABA)
│ (the Business Account ID comes from here)
│
└── Phone number
├── the TEST number Meta gives you (free,
│ limited recipients, for development)
└── your REAL number (requires verification)
Five things worth being clear on about that diagram.
The test number is your best friend at the start. Meta gives you a development phone number you can send and receive messages with for free, no business verification needed. It has one important limit: you can only converse with a short list of numbers you explicitly register (typically five). For learning, testing the agent, and doing lesson 8's mini-project, it's more than enough. That's the cheap path this guide recommends.
Business verification is for production, not for learning. Meta asks for documents proving the company exists. It takes anywhere from hours to several days. You don't need it to do this module. You need it the day you want to serve real customers with your real number.
The access token has an expiration date. The one Meta generates for you with a click in the panel is temporary — typically 24 hours — and works perfectly for testing. For something that needs to keep working tomorrow you need a long-lived token, generated from a system user in the business portfolio's settings. It's the step most people forget, and the symptom is delicious: everything worked yesterday and today nothing works, with nobody having touched anything.
A number can't be in two places at once. If a number is already registered in the WhatsApp Business app or in regular WhatsApp, it has to be removed from there before using it with the API. It's not reversible on the fly: moving a number has consequences for whoever was using it.
One single webhook per app. n8n's documentation states this explicitly and it deserves its own paragraph further below, because it's the channel's number-one practical trap.
What it costs, said plainly
None of this is free in production, and I'd rather you know that before investing a weekend.
Since July 2025, Meta charges per message delivered, not per conversation like it used to. What gets charged are templates (messages you initiate), and the price depends on three things: the template's category, the recipient's country, and your monthly volume.
| Category | What it's for | Order of magnitude |
|---|---|---|
| Marketing | Promotions, news, cart recovery | The most expensive. On the order of cents of a dollar per message, and it varies a lot between countries |
| Utility | Order confirmations, shipping notices, reminders | Considerably cheaper than marketing |
| Authentication | One-time codes | Cheap, with its own per-country pricing |
| Service | Your responses inside a conversation the customer started | No cost within the 24-hour window |
And two free windows worth knowing because they change the channel's economics:
- The 24-hour service window. When a customer writes to you, a 24-hour window opens during which you can respond freely, with regular text, no template and no cost per service message. It's the window 95% of your support agent's activity is going to live in.
- The 72-hour window for click-to-WhatsApp ads. If the customer reached your chat by clicking a Meta ad, there's a longer, charge-free window.
Prices change frequently and vary by country in a way no blog summarizes well. The source of truth is Meta's pricing page, and you have to look at it for your market, not for whatever market shows up as the example. What is stable and worth memorizing is the rule's shape, not the number: responding within the conversation is free; starting a conversation costs money and requires an approved template.
Translated to your agent: if your use case is customer support — the customer writes, the agent responds — the channel is surprisingly cheap. If your use case is the agent starting conversations — reminders, campaigns, follow-ups — then there's a bill and you need to run the numbers first.
The 24-hour rule, and why it changes the design
This is the lesson's most important section. It's the rule that separates someone who set up a WhatsApp bot from someone who understood the channel.
WhatsApp doesn't let you send someone whatever you want whenever you want. Meta's logic is simple and fairly reasonable: the customer decides when the door opens. Concretely:
The customer writes to you
│
├──► a 24-hour window opens
│ Inside it: you respond with free text, as many times as you
│ want, no template, no service-message cost.
│ Every new message from the customer RESTARTS the counter.
│
└──► 24 hours pass with no message from the customer
The window closes.
You can NO LONGER send free text.
You can only send a TEMPLATE pre-approved by Meta,
and that message gets charged according to its category.
A template (message template) is fixed text you register in Meta's panel, with slots for variables, that Meta reviews and approves before you can use it. Something like this:
# Template: order_shipped_notice (category: utility)
# Sent with the WhatsApp node → Message → Send Template
Hi {{1}}, your order {{2}} has left our
distribution center and arrives approximately on {{3}}.
If you need anything, just reply here.
The variables {{1}}, {{2}}, {{3}} get filled in when sending. The surrounding text can't be changed without going through approval again. This means something important and counterintuitive: outside the window, your agent can't improvise. A language model generates new text every time, and new text is exactly what a template doesn't allow. An AI agent can only speak freely within the 24-hour window.
Think about the design consequences, which are concrete:
A support agent lives comfortably. The customer writes, the agent responds within seconds, the window is open with room to spare. Module 5's whole system works with no changes.
A follow-up agent needs a plan. Suppose order_specialist determines a case needs investigating and the answer is going to take two days. You can't just write to the customer the day after tomorrow with the result: the window already closed. You need to send a utility-type template — pre-approved, fixed text, charged — and that template, once the customer replies to it, reopens the window and then the agent can converse. The pattern is called template to reopen, and it's the standard solution. Design for it from the start if your case is going to need it.
And a consequence almost nobody anticipates: the window's clock isn't your memory's clock. Module 3's memory node stores the history indefinitely in Postgres. WhatsApp's window expires in 24 hours. They're two independent clocks. A customer who writes back three days later opens a new window and the agent recognizes them perfectly, because memory never expired. That's fine and it's desirable — but it means the agent can reference something discussed three days ago with a customer who might have already forgotten it. It's worth having the prompt account for that: picking up old context with an anchoring phrase ("getting back to your order #4521, which we checked on Monday…") instead of continuing as if no time had passed.
n8n's two credentials
Here's a detail that causes confusion and is worth stating directly: n8n needs two distinct WhatsApp credentials, and they aren't interchangeable. One is for receiving and one is for sending.
# Credential 1 — WhatsApp API (used by the ACTION node)
# Node: WhatsApp Business Cloud
# Used for: sending messages, templates, and media
Access Token ← Meta > your app > WhatsApp > API Setup > Generate access token
Business Account ID ← the same screen, it's the WABA's ID
# Credential 2 — WhatsApp OAuth2 (used by the TRIGGER node)
# Node: WhatsApp Trigger
# Used for: registering and subscribing the webhook that receives messages
Client ID ← Meta > your app > App settings > Basic > App ID
Client Secret ← the same screen > App Secret
The reason there are two is they do different things. Sending a message is an authenticated call to WhatsApp's API with a token; subscribing to an app's events requires identifying yourself as that app, and for that Meta uses the OAuth flow with the app's ID and secret. It isn't n8n's quirk: they're two surfaces of Meta's platform.
The symptom of getting it wrong is characteristic: the agent sends perfectly but never receives anything (the trigger's credential is missing or wrong), or the reverse, the trigger fires on every incoming message but sending fails with an authorization error (the API credential's token is missing or expired).
The WhatsApp Trigger
The WhatsApp Trigger node is the one that listens. Its main parameter is the list of events it subscribes to, and n8n's documentation lists these:
Account Review Update Message Template Quality Update
Account Update Message Template Status Update
Business Capability Update Messages ◄── the one you care about
Phone Number Name Update Security
Phone Number Quality Update Template Category Update
For a conversational agent, the only one you need is Messages. The rest are for monitoring your account's health — that Meta lowered your number's quality, that a template got rejected, that the account is under review — and they're valuable in production but have nothing to do with serving customers. Subscribing to all of them "just in case" is only going to fill your execution log with noise.
The single-webhook trap
n8n's documentation says it plainly: WhatsApp only allows registering one webhook per app. It's worth understanding why that hurts in practice.
In n8n, a trigger has two URLs: the test one — which only lives while the editor is open — and the production one — which lives as long as the workflow is active. On almost every other node you can have both running at once and test in the editor without breaking production. Not on WhatsApp: there's only one place Meta sends messages to, so when you test in the editor, the production webhook stops receiving. And vice versa.
The practical ways out, in order of sensibleness:
- Two separate Meta apps, one for development and one for production, each with its own test or real number. It's the right thing to do and what everyone does once the project is serious.
- Accept the interruption while you're learning: when you test, production doesn't receive; when you're done, you reactivate the workflow. For this guide's mini-project it's perfectly acceptable.
- Test with fixed data: paste a real payload captured earlier into a data node and run the workflow from there, without depending on Meta sending anything. It's the technique that saves the most time during prompt development, because you don't depend on typing from a phone every time.
The third one is the most worth keeping in your back pocket, and to use it you need to know the payload's shape. Let's get to that.
Anatomy of the incoming message
When someone writes to your number, Meta sends a nested object to your webhook. Its general shape is this — and here's the warning: the exact structure and the path to each field depend on Meta's API version and on how your n8n version presents it, so confirm with a real execution before writing an expression:
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "<WhatsApp Business account ID>",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "5215500000000",
"phone_number_id": "<ID of the RECEIVING number>"
},
"contacts": [
{
"profile": { "name": "Ana" },
"wa_id": "5215512345678"
}
],
"messages": [
{
"from": "5215512345678",
"id": "wamid.HBgN...",
"timestamp": "1753200000",
"type": "text",
"text": { "body": "Hi, how's my order #4521 doing?" }
}
]
}
}
]
}
]
}
Four fields do all the work:
from— the customer's number. It's the stable identity lesson 1 promised: the same today, tomorrow, and in a year. It's your idealsessionId.text.body— what they wrote. Watch out: it only exists iftypeis"text". If the person sent an audio, an image, or a sticker, this field isn't there and an expression that assumes it is is going to fail.type— what kind of message it is (text,image,audio,document,interactive,button…). It's the field that tells you whether you can proceed or whether you have to handle another format.contacts[0].profile.name— the name the person has set on their WhatsApp profile. It's free and humanizes the first response a lot. It isn't a verified piece of data — anyone can put whatever they want on their profile — so it's useful for greeting, not for identifying.
And there's a fifth element that doesn't show up above and causes a very specific problem: status events. Meta doesn't just notify you when someone writes; it also notifies you when your message was delivered and when it was read. Those arrive with a statuses field instead of messages. If your workflow doesn't filter them, every message you send triggers two or three extra executions, the agent gets a payload with no text, and at best it fails and at worst it responds with something nonsensical. Filtering them is the input adapter's first line of defense.
Worked example: Module 5's system serving over WhatsApp
Let's set it up. The brain doesn't get touched: it's the same triage_agent with order_specialist and billing_specialist. What we build is the adaptation layer around it.
WhatsApp Trigger (event: Messages)
│
├─► IF: is it actually a text message?
│ (filters statuses, and also audios/images for now)
│
├─► Set: normalize_incoming ← the input adapter
│
├─► AI Agent: triage_agent ← the brain, untouched
│ ├─ Postgres Chat Memory (Session ID = the phone number)
│ ├─ AI Agent Tool: order_specialist
│ └─ AI Agent Tool: billing_specialist
│
└─► WhatsApp Business Cloud → Message → Send ← the output adapter
Step 1 — The filter. First thing, discard anything that isn't a text message:
# Node: IF — Name: is_text_message
# Only lets through events that carry a real text message.
# Without this, "delivered" and "read" notifications trigger the agent.
Condition 1: {{ $json.entry[0].changes[0].value.messages }} → exists
Condition 2: {{ $json.entry[0].changes[0].value.messages[0].type }} → equals "text"
Combine: AND
The paths above are for Meta's raw payload. Confirm the exact path on your version by running it once and looking at the trigger's output: some n8n versions un-nest part of the object and the expression gets considerably shorter.
Step 2 — The input adapter. A Set node that converts Meta's object into three clean fields. This looks trivial and it's the piece that makes lesson 7's architecture possible:
# Node: Set — Name: normalize_incoming
# Translates Meta's payload into the format the core expects.
# The names of these fields are YOURS: pick them once and
# use them the same across every channel.
channel = "whatsapp"
channel_user_id = {{ $json.entry[0].changes[0].value.messages[0].from }}
customer_name = {{ $json.entry[0].changes[0].value.contacts[0].profile.name }}
text = {{ $json.entry[0].changes[0].value.messages[0].text.body }}
Step 3 — Memory. This is where WhatsApp shines, and it's worth pausing on. On the web chat you had to invent a mechanism (metadata) to get a stable customer identity. On WhatsApp the identity comes included: the phone number is unique, it's real, and it's the same forever.
# Node: Postgres Chat Memory (connected to triage_agent)
Session ID: Define below
Key: {{ $('normalize_incoming').item.json.channel_user_id }}
That expression is literally the one from Module 3, lesson 4 — the employee credential. With it, the customer who wrote Monday and comes back Thursday picks their conversation back up without repeating anything.
Step 4 — The agent. No changes, with one precaution: the AI Agent has to know where to read the text from. Since the trigger is no longer a Chat Trigger, you have to tell it:
# Node: AI Agent — Name: triage_agent
Source for Prompt (User Message): Define below
Prompt (User Message): {{ $json.text }}
That's exactly the error Module 3 anticipated with the No session ID found message: when the trigger isn't a Chat Trigger, neither the prompt nor the session resolves on its own.
Step 5 — The output adapter. The WhatsApp Business Cloud node, Send operation:
# Node: WhatsApp Business Cloud — Name: send_whatsapp_reply
# Resource: Message · Operation: Send
Phone Number ID: <YOUR number's ID, the RECEIVING one>
Recipient Phone Number: {{ $('normalize_incoming').item.json.channel_user_id }}
Message Type: Text
Text Body: {{ $json.output }}
Two fields that frequently get confused: Phone Number ID is your business number's identifier, a long number Meta gives you that isn't a phone number; Recipient Phone Number is the person's phone, in international format with no + and no spaces. Putting one in the other's place produces an API error that doesn't clearly say which of the two is wrong.
And {{ $json.output }} is the field where the AI Agent node delivers its response. Verify the name on your version by looking at the node's output: it's one of the things that changes between versions.
What to expect. Save, activate the workflow, and write from one of the phones you registered as a test recipient: "Hi, how's my order #4521 doing?" After a few seconds — and it's going to be several, because triage_agent delegates — the response arrives on your WhatsApp. In n8n's executions tab you'll see the complete run, with its delegation trace, exactly like in Module 5. That's the whole point of the architecture: the door changed, not the brain.
Perfect. If you got here with a real message arriving on your phone, you already have the module's hardest channel working.
The node's other operations
The WhatsApp Business Cloud node does more than send text. It's worth knowing the catalog to know what's possible.
Message → Send. What you just used. Sends a message within the 24-hour window.
Message → Send Template. Sends an approved template, filling in its variables. It's the only way to write to someone outside the window. You need the template's name, the language, and the variable values in order.
Message → Send and Wait for Response. WhatsApp's equivalent of the human-in-the-loop pattern you saw in lesson 2 with the Chat node. Sends a message and pauses execution until the person answers. The response types are the same three: Approval (approve/reject buttons), Free Text (a form where they write freely), and Custom Form (a form you build yourself). It's the simplest way to get an unambiguous confirmation before a sensitive action without having to interpret an ambiguous "yes."
Media → Upload / Download / Delete. For sending and receiving files. Download is the one you'll use most: when a customer sends a photo of a damaged product or a payment receipt, the incoming message doesn't carry the file, it carries a media ID; you have to download it with that ID. It's a two-step flow that surprises people the first time.
An honest note on buttons and quick replies. WhatsApp supports interactive messages with buttons and lists, and they're a huge UX tool — lesson 6 covers them in depth. The level of native support for building them from the node varies between n8n versions; in some you have to build the interactive body with an HTTP Request node directly against Meta's API. Open the node on your installation and check what message types the Message Type selector offers before designing a conversation that depends on buttons. If they're not there, the HTTP Request route works just as well; you just have to write the JSON by hand.
Common mistakes
Installing the WhatsApp Business app and expecting it to connect (conceptual). What happens: someone downloads the app on their phone, sets up the business profile, and then spends half an hour looking for where the option to connect it to n8n is. It doesn't exist. Why it happens: the names are nearly identical and the app is the first thing that shows up when you search. How to spot it: if you're on a phone, this is it. How to fix it: the whole API flow starts in Meta's developer panel, in a browser, and never requires installing anything. And if the number you wanted to use is already in the app, it needs to be removed before registering it with the API.
The temporary token that expires overnight (practical). What happens: you set everything up, it works beautifully, you leave it active, and the next day every send fails with an authorization error while the trigger keeps receiving messages normally. Nobody touched anything. Why it happens: the token Meta generates with a click on the API setup screen is short-lived, typically 24 hours. It's meant for testing. How to spot it: the pattern is unmistakable — receives fine, sends badly, and started suddenly with no changes. How to fix it: generate a long-lived token from a system user in your business portfolio's settings, and store it in the WhatsApp API credential. And note down somewhere when it expires, because long-lived ones also expire.
Not filtering status events and triggering phantom executions (practical). What happens: for every message the agent sends, two or three more executions show up in the log, some failed and some with nonsensical responses. In bad cases, the agent responds to its own delivery receipts and generates a back-and-forth that consumes tokens with nobody using it. Why it happens: the Messages event includes both incoming messages and status changes (sent, delivered, read), and those last ones arrive with no text field. How to spot it: open one of the odd executions and look for the word statuses in the trigger's payload; if it's there, this is it. How to fix it: the IF node from step 1 of the worked example, which requires messages to exist and type to be "text". It's a two-line condition that avoids the whole problem.
Assuming every message is text (practical). What happens: the agent works fine in testing and fails the first real day, when a customer sends a voice note — completely normal on WhatsApp in LATAM — and the expression reading text.body returns empty. The agent gets a blank message and responds with something nonsensical, or the workflow fails. Why it happens: in testing you type, and real customers send audios, photos, and stickers. How to spot it: check the type field in the failed executions. How to fix it: in the short term, the type = "text" filter with an alternate branch that responds with something honest like "For now I can only read text messages, could you write it to me?" In the medium term, an audio transcription node in front of the agent turns the audio into text and the problem disappears — and that, in LATAM, is usually one of the most noticeable improvements you can make.
Designing an agent that needs to write first, with no templates (conceptual). What happens: someone builds a flow where the agent follows up 48 hours later to ask whether the problem got resolved. In testing it works, because in testing there's always an open conversation. In production, the message never arrives, and Meta's error talks about a closed window. Why it happens: the 24-hour window is invisible while you test by conversing back and forth. How to spot it: any message your system sends without the customer having written in the last 24 hours is at risk. How to fix it: register a utility-category template for that follow-up, get it approved by Meta, and send it with Send Template. The text is going to be fixed and that's fine: its only job is to reopen the window. Once the customer replies to the template, your agent can converse freely.
Exercises
Exercise 1 — Decide template or free text. For each of these five TuTienda messages, say whether it can be sent as free text or requires an approved template, and if the latter, what category you'd give it.
- Response to a customer who asked about their order thirty seconds ago.
- Notice that the order left the distribution center, two days after the last conversation.
- Second response in the same conversation, three minutes after the first.
- End-of-season promotion to every customer who bought last year.
- Result of a billing dispute that took four days to resolve.
See solution
- Free text. The window is open with plenty of room. Service message, no cost.
- Template, utility category. Two days went by, the window closed. It's transactional information about an order the customer already placed, so utility is the right category and the cheapest paid one.
- Free text. Still inside the window, and every customer message restarts it anyway.
- Template, marketing category. There's no open conversation and the content is promotional. It's the most expensive category, and also the riskiest one: if a lot of people mark it as unwanted, Meta lowers your number's quality and that affects everything else you send.
- Template, utility category. Even though the content is a case's resolution, four days went by. Notice the design pattern this forces: the template can't contain the dispute's detailed result — the text is fixed — so the sensible thing is a template saying "we have news about your case {{1}}, reply to hear about it," and once the customer replies, the window opens and the agent explains with all the detail it wants.
That last case is the most instructive in the exercise: the template doesn't convey the information, it conveys the invitation to reopen the conversation. Understanding that changes how any asynchronous flow gets designed on WhatsApp.
Why it works: all five cases get resolved with a single question — did the customer write in the last 24 hours? — and the exercise makes it automatic.
Exercise 2 — Diagnose the loop. A colleague activates their WhatsApp agent and within a few minutes sees thirty executions in the log, with nobody having written anything. Some fail; others end with the agent responding nonsensically. Explain what's happening, how you'd confirm it, and write the exact condition that fixes it.
See solution
What's happening is the status-events problem. The trigger is subscribed to Messages, and that event doesn't only carry the messages people write: it also carries status changes for the messages you send — sent, delivered, read. Every response from the agent generates two or three status events, each one triggers the workflow, and the agent ends up with a payload that has no text field.
In the worst case this feeds back on itself: if the flow doesn't fail entirely and the agent manages to send something, that something generates new statuses, which trigger it again. It isn't a strict infinite loop — a message's statuses are finite — but it does multiply executions and token consumption alarmingly.
How to confirm it: open one of the odd executions and look at the trigger's JSON. If there's a statuses key inside value instead of messages, it's confirmed.
The fix, in an IF immediately after the trigger:
# Node: IF — Name: is_text_message
Condition 1: {{ $json.entry[0].changes[0].value.messages }} → exists / not empty
Condition 2: {{ $json.entry[0].changes[0].value.messages[0].type }} → equals "text"
Combine: AND
The first condition discards status events; the second discards audios, images, and stickers, which also need to be handled separately. And confirm the path against a real execution on your version: if your n8n un-nests the payload, the expression gets shorter.
A hygiene detail worth adding: on the IF's false branch, put nothing, or put a No Operation node. Don't respond anything through that branch — responding to a delivery receipt is exactly what we're avoiding.
Why it works: the exercise teaches you to read the payload instead of guessing, which is the skill that solves 80% of any channel's problems.
Exercise 3 — Write the follow-up flow. billing_specialist opens a dispute over an unrecognized charge and the billing team takes between two and four days to resolve it. Design the complete flow to notify the customer when it's resolved. Write: (a) the template's text with its variables and category; (b) what happens when the customer replies to that template; (c) what the agent needs to know at that moment so it doesn't sound lost.
See solution
(a) The template. Category utility, because it's transactional information about a case the customer opened:
# Template: dispute_resolved_notice (utility)
Hi {{1}}, we have a resolution for the dispute {{2}} you
opened on {{3}}. Reply here and we'll share the details.
Notice what it does not say: it doesn't say whether the dispute got resolved in the customer's favor or against it. There are two reasons and both matter. The first is that a template's text is fixed, and a resolution has nuances that don't fit into variables. The second is a product one: bad news doesn't get delivered in a message that expects no reply. The template opens the door; the agent converses.
(b) When the customer replies. Their reply arrives through the WhatsApp Trigger like any other message, opens the 24-hour window, and enters triage_agent normally. From there it's a regular ordinary conversation.
(c) What the agent needs to know. Here's the interesting part, and it's where the exercise gets tested. If the customer replies with just "yes, tell me", the agent gets a message with no reference to the dispute at all. Three things save it, and it's worth having all three:
- Persistent memory. Since the
sessionIdis the phone number, the history from when the dispute got opened is still there. The agent can read that disputeD-8842got opened four days ago. - A case record queryable by tool. Even better: a
lookup_disputetool that, given the phone number or the ID, returns the current status. Memory says what got discussed; the tool says what actually happened afterward, which isn't the same thing. - An anchoring instruction in the prompt. Something like: "if the customer replies to a follow-up notification, explicitly pick back up the case it refers to before giving the detail, because days may have passed since the last conversation." Without that, the agent might respond correctly but with no context, and the customer gets hit with a resolution without remembering which case it's about.
The superior version of the design — worth mentioning — is logging in your own database that that phone number was sent the dispute_resolved_notice template for case D-8842. That way, when any message arrives from that number in the following hours, the input adapter can inject that context directly. It's more work and it's what separates a flow that works from one that feels good.
Why it works: the exercise brings together this lesson's three new things — the window, the template, and the memory that doesn't expire with it — into a flow that shows up in any real support system.
Summary and next step
You now have LATAM's most important channel working with Module 5's brain behind it. You told the API apart from the app it shares a name with, put together Meta's pieces and know which of them is a production procedure and which is a development shortcut, created the two credentials n8n needs — OAuth2 for receiving, API token for sending — and know how to recognize the symptom of having one of them wrong. You filtered the status events that otherwise trigger phantom executions, normalized the payload into three clean fields, and used the customer's phone number as the persistent memory key, which is the best identity any channel is going to hand you for free.
And above all, you understood the rule governing the design: within the 24-hour window your agent converses freely and for free; outside it, it can only send an approved, paid template, whose real job isn't informing but reopening the door.
Before moving on you should be able to: explain in one sentence why n8n needs two WhatsApp credentials and what breaks if you only have one; say what happens to the test URL when you activate the production workflow, and why; and write from memory the condition that discards status events.
Lesson 4 deliberately lowers the difficulty. Telegram does almost everything WhatsApp does — trigger, messages, buttons — for free, no business verification, no 24-hour window, and with a bot created in two minutes talking to another bot. It's the lab where you're going to practice interactive buttons and the callback pattern that later carries over to WhatsApp, and it's the complete alternative if your Meta verification is still pending.
Resources
- WhatsApp Trigger node — n8n Docs — the complete list of events and the official warning about the single webhook per app.
- WhatsApp Business Cloud node — n8n Docs — the message and media operations; confirm there which message types your version offers before designing with buttons.
- WhatsApp credentials — n8n Docs — the step-by-step for the two credentials, with the exact Meta screens each value comes from.
- WhatsApp Cloud API — Meta for Developers — the source of truth for the incoming payload, message types, and interactive messages.
- WhatsApp Business Platform pricing — Meta for Developers — the categories, the charge-free windows, and per-country pricing. Look at it for your market, not for whatever blog example you find.
- Message templates — Meta for Developers — how they get registered, what Meta reviews, and why a rejected template is usually rejected over its category and not its text.