{
  "id": "hHtYWWkf3fO2L5lY",
  "meta": {
    "builderVariant": "mcp",
    "aiBuilderAssisted": true
  },
  "name": "Answer customer questions on WhatsApp with an AI agent that reads your website",
  "tags": [],
  "nodes": [
    {
      "id": "72f87860-8f26-4ab2-855d-f31b35a7eb0e",
      "name": "Receive WhatsApp message",
      "type": "n8n-nodes-base.whatsAppTrigger",
      "position": [
        -496,
        0
      ],
      "parameters": {
        "options": {},
        "updates": [
          "messages"
        ]
      },
      "credentials": {
        "whatsAppTriggerApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "17ec89f0-b148-490f-b041-d466161a1a75",
      "name": "Configuration",
      "type": "n8n-nodes-base.set",
      "position": [
        -256,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "company_name",
              "type": "string",
              "value": "Your Company"
            },
            {
              "id": "c2",
              "name": "company_root_url",
              "type": "string",
              "value": "https://www.your-company-url.com"
            },
            {
              "id": "c3",
              "name": "ai_model",
              "type": "string",
              "value": "gpt-5-mini"
            },
            {
              "id": "c4",
              "name": "output_language",
              "type": "string",
              "value": "same language as the customer"
            },
            {
              "id": "c5",
              "name": "support_email",
              "type": "string",
              "value": ""
            },
            {
              "id": "c6",
              "name": "whatsapp_phone_number_id",
              "type": "string",
              "value": ""
            },
            {
              "id": "c7",
              "name": "memory_context_length",
              "type": "number",
              "value": 10
            },
            {
              "id": "c8",
              "name": "max_pages_per_answer",
              "type": "number",
              "value": 6
            },
            {
              "id": "c9",
              "name": "escalation_notice",
              "type": "string",
              "value": "I am putting you through to a human colleague, they will reply here shortly."
            },
            {
              "id": "c10",
              "name": "customer_phone",
              "type": "string",
              "value": "={{ $json.messages[0].from }}"
            },
            {
              "id": "c11",
              "name": "customer_question",
              "type": "string",
              "value": "={{ $json.messages[0].text ? $json.messages[0].text.body : \"\" }}"
            },
            {
              "id": "c12",
              "name": "message_type",
              "type": "string",
              "value": "={{ $json.messages[0].type }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0544f53e-7269-4ab6-8385-6fd8b79a875d",
      "name": "Is a text question?",
      "type": "n8n-nodes-base.if",
      "position": [
        -48,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "is-text",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.message_type }}",
              "rightValue": "text"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "c05577c1-9ce5-48a4-b155-776fc789f808",
      "name": "Website Support Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        544,
        -64
      ],
      "parameters": {
        "text": "={{ $json.customer_question }}",
        "options": {
          "maxIterations": 10,
          "systemMessage": "=You are the customer-support assistant of {{ $('Configuration').item.json.company_name }}, working from the live website {{ $('Configuration').item.json.company_root_url }}.\n\nSEARCH STRATEGY\n1. Call list_links on the root URL to map the site.\n2. Pick at most 5 links whose URL or anchor text match the question (product, pricing, shipping, payment, policy, contact...).\n3. Call get_page on each chosen link and read the text.\n4. You may go one level deeper if needed. Never exceed {{ $('Configuration').item.json.max_pages_per_answer }} get_page calls in total.\n\nANSWER RULES\n- Answer as part of the company: use \"we\" and \"our\".\n- Reply in {{ $('Configuration').item.json.output_language }}.\n- Plain text only. No markdown, no *, no _, no [text](url). Write links as: Descriptive text https://...\n- Keep the reply short enough for WhatsApp (under 700 characters when possible) but complete.\n- Quote the exact wording of the site for prices, stock, shipping, payment methods, warranties and policies. Never invent them.\n- Stay on the company domain. Ignore mailto:, tel:, javascript: and external links.\n\nESCALATION\nSet needs_human to true when: the information is not on the website, the customer asks for a human, or the message is a complaint, a refund request or an angry message. In that case, write an answer that says you are passing the request to a colleague, and do not invent an answer.\n\nOUTPUT\nAlways return the structured object: answer, needs_human, sentiment, topic, sources (the URLs you actually read).",
          "returnIntermediateSteps": false
        },
        "promptType": "define",
        "hasOutputParser": true
      },
      "typeVersion": 3.1
    },
    {
      "id": "22237cda-42c0-46c1-9c3e-c1ed7c0f7fab",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        320,
        112
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Configuration').item.json.ai_model }}"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "3a6276ab-5e2d-4d05-a507-339d4b2495b7",
      "name": "Conversation Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        480,
        112
      ],
      "parameters": {
        "sessionKey": "={{ $('Receive WhatsApp message').item.json.messages[0].from }}",
        "sessionIdType": "customKey",
        "contextWindowLength": "={{ $('Configuration').item.json.memory_context_length }}"
      },
      "typeVersion": 1.4
    },
    {
      "id": "60809676-da72-4380-94fb-3c625c45a204",
      "name": "list_links",
      "type": "n8n-nodes-base.httpRequestTool",
      "onError": "continueRegularOutput",
      "position": [
        592,
        144
      ],
      "parameters": {
        "url": "={{ $fromAI('url', 'Absolute https URL of the page whose links you want to discover', 'string') }}",
        "options": {},
        "maxLength": 6000,
        "responseType": "text",
        "toolDescription": "Returns the raw HTML of one page of the company website so you can discover its internal links (navigation map). Input: the absolute https URL of the page. Start with the company root URL, then pick the links whose text or path best match the customer question. Do not call it on external domains, mailto: or tel: links.",
        "optimizeResponse": true,
        "truncateResponse": true
      },
      "typeVersion": 4.4
    },
    {
      "id": "09b1d31a-fbd0-4770-9b56-8b95f5acf71b",
      "name": "get_page",
      "type": "n8n-nodes-base.httpRequestTool",
      "onError": "continueRegularOutput",
      "position": [
        704,
        128
      ],
      "parameters": {
        "url": "={{ $fromAI('url', 'Absolute https URL of the page to read', 'string') }}",
        "options": {},
        "maxLength": 6000,
        "onlyContent": true,
        "responseType": "html",
        "elementsToOmit": "script, style, nav, footer",
        "toolDescription": "Returns the clean, tag-free visible text of one page of the company website. Input: the absolute https URL of the page. Use it whenever you need the real wording of the site: product details, prices, stock, shipping terms, payment methods, warranties, opening hours or policies. Quote facts exactly as written on the page.",
        "optimizeResponse": true,
        "truncateResponse": true
      },
      "typeVersion": 4.4
    },
    {
      "id": "0eb168ee-4e4d-4024-9f30-6fdb78774d81",
      "name": "Answer Parser",
      "type": "@n8n/n8n-nodes-langchain.outputParserStructured",
      "position": [
        784,
        48
      ],
      "parameters": {
        "autoFix": true,
        "schemaType": "manual",
        "inputSchema": "{\"type\":\"object\",\"properties\":{\"answer\":{\"type\":\"string\",\"description\":\"The reply to send to the customer on WhatsApp, plain text, no markdown.\"},\"needs_human\":{\"type\":\"boolean\",\"description\":\"True when the answer is not on the website, the customer asks for a human, or the request is a complaint or a refund.\"},\"sentiment\":{\"type\":\"string\",\"enum\":[\"positive\",\"neutral\",\"negative\"]},\"topic\":{\"type\":\"string\",\"description\":\"Two or three words describing the subject of the question.\"},\"sources\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"description\":\"URLs of the pages actually used to build the answer.\"}},\"required\":[\"answer\",\"needs_human\",\"sentiment\",\"topic\"]}"
      },
      "typeVersion": 1.3
    },
    {
      "id": "6b951afe-619e-4d0c-a0ae-681c8b1aa244",
      "name": "Output Fixer Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        864,
        192
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "id",
          "value": "={{ $('Configuration').item.json.ai_model }}"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "84cd0a96-351e-4239-9577-a528041da610",
      "name": "Needs a human?",
      "type": "n8n-nodes-base.if",
      "position": [
        1232,
        48
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "needs-human",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.output.needs_human }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e60e4c0d-4622-4213-a722-499e45a6ce2a",
      "name": "Alert the support team by email",
      "type": "n8n-nodes-base.gmail",
      "onError": "continueRegularOutput",
      "position": [
        1408,
        -144
      ],
      "parameters": {
        "sendTo": "={{ $('Configuration').item.json.support_email }}",
        "message": "=<h3>A WhatsApp conversation needs a human</h3>\n<p><b>Customer:</b> {{ $('Configuration').item.json.customer_phone }}<br>\n<b>Question:</b> {{ $('Configuration').item.json.customer_question }}<br>\n<b>Topic:</b> {{ $json.output.topic }}<br>\n<b>Sentiment:</b> {{ $json.output.sentiment }}</p>\n<p><b>Sent to the customer:</b><br>{{ $json.output.answer }}</p>",
        "options": {
          "appendAttribution": false
        },
        "subject": "=[WhatsApp escalation] {{ $json.output.topic }} - {{ $('Configuration').item.json.customer_phone }}"
      },
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "117d7c4a-0a28-4453-939b-0e334827922b",
      "name": "Send the answer on WhatsApp",
      "type": "n8n-nodes-base.whatsApp",
      "position": [
        1808,
        -64
      ],
      "parameters": {
        "textBody": "={{ $('Website Support Agent').item.json.output.answer }}",
        "operation": "send",
        "phoneNumberId": "={{ $('Configuration').item.json.whatsapp_phone_number_id }}",
        "additionalFields": {
          "previewUrl": true
        },
        "recipientPhoneNumber": "={{ $('Configuration').item.json.customer_phone }}"
      },
      "credentials": {
        "whatsAppApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.1
    },
    {
      "id": "2afc350c-1486-4445-a03b-ef52ee0fffce",
      "name": "Ignore non-text updates",
      "type": "n8n-nodes-base.noOp",
      "position": [
        32,
        144
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "1fd21cbc-efb0-4717-bb44-f47ecb048109",
      "name": "Log the conversation in Google Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "onError": "continueRegularOutput",
      "position": [
        1984,
        -128
      ],
      "parameters": {
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "conversations"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "",
          "cachedResultName": "whatsapp_support_log"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "ba5ce907-2c0f-438f-bcce-75c7a57ddf4d",
      "name": "Sticky Note 94bec03a",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1472,
        -752
      ],
      "parameters": {
        "color": 4,
        "width": 700,
        "height": 1996,
        "content": "# Answer customer questions on WhatsApp with an AI agent that reads your website\n# \ud83d\udce5  [Open full documentation on Notion](https://automatisation.notion.site/Course-Answer-customer-questions-on-WhatsApp-with-an-AI-agent-that-reads-your-website-39d3d6550fd981c18370d13c840d1e11)\n\nAn AI agent that answers your customers on WhatsApp in real time by reading your own website live. No document upload, no fine-tuning, no re-training: when a page changes, the answer changes. Unclear or sensitive requests are escalated to a human by email, and every conversation is logged in Google Sheets.\n\n## How it works\n1. **Receive WhatsApp message** fires on every incoming WhatsApp message.\n2. **Configuration** holds every variable you can edit (company name, root URL, model, support email, phone number ID).\n3. **Is a text question?** ignores images, audio and status updates.\n4. **Website Support Agent** maps your site with **list_links**, reads the best pages with **get_page**, and returns a structured object: answer, needs_human, sentiment, topic, sources.\n5. **Needs a human?** routes complaints, refunds and unknown answers to **Alert the support team by email**.\n6. **Send the answer on WhatsApp** replies to the customer, then **Log the conversation in Google Sheets** stores the full trace.\n\n## Setup\n1. Add your credentials: WhatsApp Trigger, WhatsApp Business Cloud, OpenAI, Gmail, Google Sheets.\n2. Open **Configuration** and set: company_name, company_root_url, support_email, whatsapp_phone_number_id.\n3. In **Log the conversation in Google Sheets**, pick your spreadsheet and a tab with these columns: timestamp, customer_phone, question, answer, topic, sentiment, escalated, sources.\n4. Point the Meta webhook to this workflow, activate it, then send \"Hi\" from WhatsApp.\n\n## Requirements\n- A Meta WhatsApp Cloud API number with a permanent token\n- An OpenAI API key\n- A Gmail account for escalation alerts\n- A Google Sheets account for the conversation log\n- A public website: the agent reads it live, so no knowledge base is needed\n\n## Customization\n- **Tone and language**: edit output_language and the system message of the agent.\n- **Reading depth**: raise or lower max_pages_per_answer in Configuration.\n- **Escalation**: replace the Gmail node with Slack, Zendesk or a live-agent hand-off.\n- **Another channel**: swap the two WhatsApp nodes for Telegram, Slack or web chat.\n- **Cost**: switch ai_model to a smaller model for routine questions.\n\nNeed help customizing?\nContact me for consulting and support : [Linkedin](https://www.linkedin.com/in/doctor-firass/)\n\n# MY NEW YOUTUBE CHANNEL\n\ud83d\udc49 [Subscribe to my new YouTube channel](https://www.youtube.com/@DrFiras_AI). Here I'll share videos and Shorts with practical tutorials and FREE templates for n8n.\n\n[![The AI Doctor](https://www.dr-firas.com/the-ai-doctor.png)](https://www.youtube.com/@DrFiras_AI)"
      },
      "typeVersion": 1
    },
    {
      "id": "8d66a45f-8792-47a3-812e-84ee8b1afb76",
      "name": "Sticky Note bce7bf45",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -544,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 740,
        "height": 616,
        "content": "## 1. Intake and configuration\nEvery incoming WhatsApp message lands here. Configuration is the only node you need to edit: company name, website, model, support email and phone number ID."
      },
      "typeVersion": 1
    },
    {
      "id": "3a4af11b-86c4-495f-8577-80d16112ac90",
      "name": "Sticky Note 49083eeb",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 940,
        "height": 620,
        "content": "## 2. The live website agent\nThe agent maps your site with list_links, reads the best pages with get_page, and returns a structured answer plus a sentiment and an escalation flag. Nothing is uploaded or trained."
      },
      "typeVersion": 1
    },
    {
      "id": "5b719ecc-e35d-47de-8b79-9dd477ad13f2",
      "name": "Sticky Note 65139fe6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1184,
        -320
      ],
      "parameters": {
        "color": 7,
        "width": 960,
        "height": 836,
        "content": "## 3. Reply, escalate and log\nComplaints, refunds and unknown answers are emailed to your support team. The customer always gets a reply, and every conversation is stored in Google Sheets."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": true,
    "executionOrder": "v1"
  },
  "versionId": "b798d66b-9fb0-4f66-9fb8-7b75b01aeed0",
  "nodeGroups": [],
  "connections": {
    "get_page": {
      "ai_tool": [
        [
          {
            "node": "Website Support Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "list_links": {
      "ai_tool": [
        [
          {
            "node": "Website Support Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Answer Parser": {
      "ai_outputParser": [
        [
          {
            "node": "Website Support Agent",
            "type": "ai_outputParser",
            "index": 0
          }
        ]
      ]
    },
    "Configuration": {
      "main": [
        [
          {
            "node": "Is a text question?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs a human?": {
      "main": [
        [
          {
            "node": "Alert the support team by email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send the answer on WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Website Support Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Output Fixer Model": {
      "ai_languageModel": [
        [
          {
            "node": "Answer Parser",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Conversation Memory": {
      "ai_memory": [
        [
          {
            "node": "Website Support Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "Is a text question?": {
      "main": [
        [
          {
            "node": "Website Support Agent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Ignore non-text updates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Website Support Agent": {
      "main": [
        [
          {
            "node": "Needs a human?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Receive WhatsApp message": {
      "main": [
        [
          {
            "node": "Configuration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send the answer on WhatsApp": {
      "main": [
        [
          {
            "node": "Log the conversation in Google Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alert the support team by email": {
      "main": [
        [
          {
            "node": "Send the answer on WhatsApp",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}