Skip to content

Goal

Customers message small businesses on WhatsApp and wait hours. Build an assistant that answers right away from the business's own facts, books jobs, and knows when to step aside for a human.

Prerequisites

  • Modules 08 (AI agent), 13 (static data, Switch routing), 15 (AI with a template fallback), 19 (multi-trigger workflow, injected clock)
  • For the live part: a Meta developer app with the WhatsApp product (the test number is free), and an OpenAI key

Concepts

  • WhatsApp Cloud API webhooks. Meta first verifies your URL with a GET (hub.mode, hub.verify_token, hub.challenge): echo the challenge if the token matches. Then it POSTs events: entry[].changes[].value holds messages[], contacts[] (profile names) and statuses[] (sent/delivered/read). One POST can hold several messages, or none.
  • Dedupe. Meta retries a webhook if it doesn't get a 200 fast enough. Store recent message ids per contact and ignore repeats, or customers get double replies.
  • Rules before AI. Anything that must never go wrong is decided by code before the model sees it: opt-out, safety keywords ("I smell gas" always gets the same safety message), handoff requests, and "a human has this chat, stay quiet".
  • AI with fallback. The model gets only the facts from Settings, the lead so far and recent history, and returns structured output: reply, intent, handoff, lead. If it fails (no key, timeout, bad output) the rule-based reply goes out instead.
  • Conversation state. Per contact in static data: last 40 messages, lead fields, which question we asked last (awaiting), opt-out, handoff timer, and the 24-hour window: WhatsApp lets you send free-form messages only within 24 h of the customer's last message; after that you need an approved template (Exercise 3).
  • Opt-out and consent. Honor STOP immediately, and only message people who contacted you or opted in. Check WhatsApp's Business Messaging Policy; this is not legal advice.

Meta setup (once)

  1. developers.facebook.com → Create app → Business → add WhatsApp. You get a test phone number, its Phone number ID, and a temporary access token.
  2. Add your own phone as a test recipient.
  3. In n8n create a WhatsApp API credential (access token + business account ID) for sending, and a WhatsApp OAuth credential (app ID + secret) for the trigger.
  4. Put the Phone number ID in Settings → phone_number_id.
  5. Webhook: either activate the WhatsApp Trigger (n8n registers the URL and checks Meta's signature), or point Meta's Callback URL at https://your-n8n/webhook/wa/inbound with Settings → verify_token, subscribed to messages. Meta needs a public HTTPS URL (module 12, or a tunnel).

Build it

Step 1: Triggers, Settings, Route

  • WhatsApp Trigger (updates: messages), Webhooks: Inbound API (POST wa/inbound), Verify API (GET wa/inbound, same path, different method), Conversation API (GET wa/conversation), Release Handoff API (POST wa/handoff/release). All with Respond: Using 'Respond to Webhook' node.
  • Edit Fields Settings (edit me): business name, phone number ID, verify token, use_ai, timezone, hours per weekday, service area + service_zips, services (name + keywords), faq (keywords + answer), emergency_keywords, handoff hours, memory turns.
  • Switch Route: message / verify / conversation / release.

Step 2: Verification

IF Token Matches? (hub.mode == subscribe and token matches) → Respond Text {{ $('Verify API').first().json.query['hub.challenge'] }}; else Respond 403.

Step 3: Messages

  1. Code Parse Messages (parse.js): webhook body or trigger items → one item per message (wa_id, profile name, id, type, text incl. button replies and captions). No messages → ignored (respond only if called via API).
  2. Code Conversation State (state.js): load or create the conversation, mark duplicates, check business hours with Luxon, apply the rules in order, and compute the rule-based reply for answer messages.
  3. IF Use AI? → Basic LLM Chain Answer with AI (prompt in code/ai-prompt.txt) with OpenAI Chat Model (gpt-5-mini) and Structured Output Parser Reply Format; On Error → Continue.
  4. Code Pick Reply (pick.js): AI reply if there is one, else the rule reply. It runs for both IF branches; itemMatching(i) pairs AI output with its message.
  5. Code Save Turn (apply.js): history, lead fields, awaiting, opt-out, handoff timer, lead captured once (only for ZIPs in the area). Skipped on dry runs.
  6. IF Dry Run? → respond with the plan. Else IF Has Reply? → WhatsApp Message → Send (phone number ID from Settings, recipient wa_id, text) with On Error → Continue.
  7. Code Reply Report (report.js): sent only if WhatsApp returned a message id → respond (API only), Save Lead (Exercise 1), Alert Staff (Exercise 2).

Step 4: Status and release

Conversation Status returns one conversation (404 if unknown). Release Handoff clears the handoff timer.

Publish (static data only persists for production executions).

Test it

./test/test.sh                                         # 51 assertions, no credentials
WITH_CREDS=1 TEST_WA_ID=15125550123 ./test/test.sh     # real AI reply sent to your phone

Try a conversation by hand:

curl -X POST 'http://localhost:5678/webhook/wa/inbound?dry_run=true' -H 'Content-Type: application/json' -d '{
  "object":"whatsapp_business_account","entry":[{"changes":[{"field":"messages","value":{
  "metadata":{"phone_number_id":"123456789012345"},"contacts":[{"profile":{"name":"Sam"},"wa_id":"15125550123"}],
  "messages":[{"from":"15125550123","id":"wamid.demo1","timestamp":"1792000000","type":"text","text":{"body":"How much is a tune-up?"}}]}}]}]}'

Common problems

  • Meta says the callback URL couldn't be verified: the URL must be public HTTPS, the token must match verify_token, and the workflow must be published.
  • Customer gets the same reply twice: dedupe only works in production executions (static data isn't saved on manual runs).
  • "(#131030) Recipient phone number not in allowed list": with the test number you can only message recipients you added in the Meta app.
  • "Re-engagement message" error: the 24-hour window closed; you need a template message (Exercise 3).
  • AI replies with made-up prices: tighten the facts in Settings and the prompt rules; keep prices in the FAQ, not in the model's head.