This workflow corresponds to n8n.io template #13450 — we link there as the canonical source.
This workflow follows the Agent → Google Calendar recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "OEKHk1t8dnJ9CA0e",
"name": "Auto-respond and classify WhatsApp leads with AI and log to Google Sheets",
"tags": [
{
"id": "mHQntpbppFUpZPGm",
"name": "template",
"createdAt": "2026-02-17T08:30:26.885Z",
"updatedAt": "2026-02-17T08:30:26.885Z"
}
],
"nodes": [
{
"id": "59e9c93e-bcf1-4e6b-a773-b689bba5c0e0",
"name": "\ud83d\udd27 Parse WhatsApp Message",
"type": "n8n-nodes-base.code",
"position": [
0,
752
],
"parameters": {
"jsCode": "// Parse WhatsApp messages with loop prevention\nconst input = $input.all()[0].json;\nlet output = {};\n\n// \u26a0\ufe0f UPDATE THESE to match your WhatsApp Business phone number ID and display number\nconst BOT_NUMBERS = ['YOUR_PHONE_NUMBER_ID', 'YOUR_DISPLAY_PHONE_NUMBER'];\n\nconst isBot = (number) => {\n if (!number) return false;\n const cleanNumber = String(number).replace(/\\D/g, '');\n return BOT_NUMBERS.some(botNum => \n cleanNumber.includes(botNum.replace(/\\D/g, ''))\n );\n};\n\n// Process INCOMING messages only\nif (input.messages && input.messages[0]) {\n const message = input.messages[0];\n const contact = input.contacts?.[0] || {};\n \n // Ignore bot's own messages\n if (isBot(message.from)) {\n return {\n json: {\n eventType: 'outgoing',\n skipProcessing: true,\n receivedAt: new Date().toISOString()\n }\n };\n }\n \n output = {\n eventType: 'message',\n skipProcessing: false,\n from: message.from,\n messageId: message.id,\n timestamp: message.timestamp,\n name: contact.profile?.name || 'Unknown',\n messageBody: message.text?.body || '',\n messageType: message.type || 'text',\n waId: contact.wa_id || message.from,\n phoneNumberId: input.metadata?.phone_number_id || '',\n receivedAt: new Date(parseInt(message.timestamp) * 1000).toISOString()\n };\n}\n// Process STATUS updates (skip)\nelse if (input.statuses && input.statuses[0]) {\n return {\n json: {\n eventType: 'status',\n skipProcessing: true,\n receivedAt: new Date().toISOString()\n }\n };\n}\n// Unknown format\nelse {\n return {\n json: {\n eventType: 'unknown',\n skipProcessing: true,\n receivedAt: new Date().toISOString()\n }\n };\n}\n\nreturn { json: output };"
},
"typeVersion": 2
},
{
"id": "a68f112c-f642-4cae-b506-c333f8481f26",
"name": "\ud83e\udd16 AI Intent Classifier",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
224,
752
],
"parameters": {
"text": "=Classify this WhatsApp message.\n\nFrom: {{ $json.name }}\nPhone: {{ $json.from }}\nMessage: {{ $json.messageBody }}",
"options": {
"systemMessage": "You are an intelligent lead qualification assistant for WhatsApp business messages.\n\nYour job is to analyze the incoming message and classify it into one of FOUR categories.\n\nIMPORTANT: You must determine if the message has ENOUGH information to be actionable, or if it requires further qualification.\n\n---\n\nINTENT CATEGORIES:\n\n1. hot_lead - Customer has provided COMPLETE information AND shows urgent buying intent.\n REQUIRED signals (must have MULTIPLE):\n - Explicit pricing, quote, or demo request\n - Mentioned budget, timeline, or urgency\n - Provided company name, industry, team size, or specific use case\n - Language showing they are ready to proceed\n Example: \"Hi, I run a 50-person SaaS company. We need WhatsApp automation for sales follow-ups. What is your pricing for 10 agents? We want to start in 2 weeks.\"\n\n2. warm_lead - Customer shows GENUINE interest AND has provided SOME qualifying context.\n REQUIRED signals:\n - Asking about features, capabilities, how it works\n - Mentioned their business type or use case (even briefly)\n - Evaluating or comparing solutions\n - Has substance beyond just a greeting\n Example: \"I saw your WhatsApp automation service. How does it work and what kind of businesses do you help?\"\n Example: \"We are an e-commerce company looking for customer support automation. What features do you offer?\"\n\n3. support - Existing user with a technical or account issue.\n REQUIRED signals:\n - References existing setup, account, integration, or prior purchase\n - Describes specific technical issue or malfunction\n - Language like \"stopped working\", \"can not login\", \"since yesterday\"\n Example: \"Our WhatsApp messages are not getting logged in the CRM since yesterday. Can you help?\"\n\n4. needs_qualification - Message does NOT have enough context to classify as hot, warm, or support.\n This includes:\n - Greetings only: \"Hi\", \"Hello\", \"Hey\", \"Good morning\"\n - Vague messages: \"I need help\", \"Can you help me?\", \"Tell me more\"\n - Single word or very short messages with no context\n - Pricing questions with ZERO context: \"What is your pricing?\" (no industry, no use case, no company info)\n - \"How are you?\", \"Is anyone there?\", \"Can I talk to someone?\"\n - Any message where you genuinely cannot determine what the customer wants or needs\n Example: \"Hi\"\n Example: \"What is your pricing?\" (without any context)\n Example: \"I need help with something\"\n Example: \"Hello, good morning\"\n\n---\n\nCRITICAL RULES:\n\n1. If the message is JUST a greeting with no substance, it is ALWAYS needs_qualification. Never classify \"Hi\" or \"Hello\" as warm_lead.\n\n2. If someone asks about pricing but provides NO context about who they are, what industry, what use case, or what size company, it is needs_qualification, NOT warm_lead or hot_lead.\n\n3. warm_lead requires the customer to have shared SOME meaningful context about their business or needs, not just a generic question.\n\n4. hot_lead requires MULTIPLE qualifying signals including urgency or readiness to buy.\n\n5. support requires clear reference to an EXISTING product, account, or setup.\n\n6. When in doubt between warm_lead and needs_qualification, choose needs_qualification. It is better to ask a qualifying question than to misroute.\n\n7. For needs_qualification, always provide a friendly, specific nextQuestion that will help qualify the lead.\n\n---\n\nFor needs_qualification, generate a nextQuestion that is:\n- Friendly and conversational (include emoji)\n- ONE specific question (not multiple)\n- Designed to understand their business need\n- Examples:\n - For \"Hi\": \"Hi there! Thanks for reaching out. How can I help you today?\"\n - For \"What is your pricing?\": \"I would love to help with pricing! Could you tell me a bit about your business and what you are looking to achieve?\"\n - For \"I need help\": \"Of course! Could you tell me more about what you need help with?\"\n\n---\n\nRESPOND WITH ONLY RAW JSON. No markdown fences. No explanation. Start with { end with }.\n\n{\n \"intent\": \"hot_lead\" or \"warm_lead\" or \"support\" or \"needs_qualification\",\n \"confidence\": 0.0 to 1.0,\n \"reason\": \"Brief explanation of why this classification was chosen\",\n \"hasEnoughInfo\": true or false,\n \"missingInfo\": [\"list\", \"of\", \"missing\", \"items\"] or [],\n \"nextQuestion\": \"The follow-up question to ask\" or \"\"\n}"
},
"promptType": "define",
"hasOutputParser": true
},
"typeVersion": 2.1
},
{
"id": "e0d448a3-cb12-44a1-92d8-5cc102cdc4e0",
"name": "\ud83c\udfaf Extract Classification",
"type": "n8n-nodes-base.code",
"position": [
512,
752
],
"parameters": {
"jsCode": "const messageData = $node[\"\ud83d\udd27 Parse WhatsApp Message\"].json;\nconst rawAiOutput = $json;\n\nlet aiOutput = {};\n\nfunction extractJSON(str) {\n if (typeof str !== 'string') return null;\n let cleaned = str.replace(/```json\\s*/gi, '').replace(/```\\s*/g, '').trim();\n try {\n return JSON.parse(cleaned);\n } catch (e) {\n const match = cleaned.match(/\\{[\\s\\S]*\"intent\"\\s*:[\\s\\S]*\\}/);\n if (match) {\n try { return JSON.parse(match[0]); } catch (e2) { return null; }\n }\n return null;\n }\n}\n\nfunction findAIResponse(obj, depth = 0) {\n if (depth > 5 || !obj || typeof obj !== 'object') return null;\n if (obj.intent && typeof obj.intent === 'string') return obj;\n \n const props = ['output', 'text', 'message', 'content', 'response', 'result', 'data', 'json', 'kwargs', 'lc_kwargs'];\n \n for (const prop of props) {\n if (obj[prop] !== undefined) {\n if (typeof obj[prop] === 'string') {\n const parsed = extractJSON(obj[prop]);\n if (parsed && parsed.intent) return parsed;\n } else if (typeof obj[prop] === 'object') {\n const found = findAIResponse(obj[prop], depth + 1);\n if (found) return found;\n }\n }\n }\n \n for (const key of Object.keys(obj)) {\n if (props.includes(key)) continue;\n const val = obj[key];\n if (typeof val === 'string' && val.includes('\"intent\"')) {\n const parsed = extractJSON(val);\n if (parsed && parsed.intent) return parsed;\n } else if (typeof val === 'object' && val !== null) {\n const found = findAIResponse(val, depth + 1);\n if (found) return found;\n }\n }\n return null;\n}\n\ntry {\n if (rawAiOutput && rawAiOutput.intent) {\n aiOutput = rawAiOutput;\n } else {\n const found = findAIResponse(rawAiOutput);\n if (found) aiOutput = found;\n }\n \n if (!aiOutput.intent) {\n const fullString = JSON.stringify(rawAiOutput);\n const parsed = extractJSON(fullString);\n if (parsed && parsed.intent) aiOutput = parsed;\n }\n \n if (!aiOutput.intent && typeof rawAiOutput === 'string') {\n const parsed = extractJSON(rawAiOutput);\n if (parsed && parsed.intent) aiOutput = parsed;\n }\n} catch (error) {}\n\nconst validIntents = ['hot_lead', 'warm_lead', 'support', 'spam', 'needs_qualification'];\n\nif (!aiOutput.intent || !validIntents.includes(aiOutput.intent)) {\n if (!aiOutput.intent) {\n aiOutput = {\n intent: 'needs_qualification',\n confidence: 0.5,\n reason: 'Could not parse AI response - defaulting to qualification',\n hasEnoughInfo: false,\n missingInfo: ['parsing failed - needs manual review'],\n nextQuestion: 'Hi! Thanks for reaching out \ud83d\ude0a How can I help you today?'\n };\n } else {\n aiOutput.intent = 'needs_qualification';\n }\n}\n\nconst result = {\n from: messageData.from || '',\n name: messageData.name || '',\n messageBody: messageData.messageBody || '',\n messageId: messageData.messageId || '',\n messageType: messageData.messageType || 'text',\n timestamp: messageData.timestamp || '',\n receivedAt: messageData.receivedAt || '',\n waId: messageData.waId || '',\n skipProcessing: messageData.skipProcessing || false,\n phoneNumberId: messageData.phoneNumberId || '',\n intent: aiOutput.intent,\n confidence: parseFloat(aiOutput.confidence) || 0,\n reason: aiOutput.reason || 'No reason provided',\n hasEnoughInfo: aiOutput.hasEnoughInfo === true,\n missingInfo: Array.isArray(aiOutput.missingInfo) ? aiOutput.missingInfo : [],\n nextQuestion: aiOutput.nextQuestion || '',\n processedAt: new Date().toISOString(),\n model: 'your-ai-model'\n};\n\nreturn { json: result };"
},
"typeVersion": 2
},
{
"id": "29f47043-c208-41e5-b733-3c8d0c3d7e59",
"name": "\ud83d\udd00 Route by Intent",
"type": "n8n-nodes-base.switch",
"position": [
752,
752
],
"parameters": {
"rules": {
"values": [
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e6398bcf-f232-42e8-a25c-39e3d6796550",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.intent }}",
"rightValue": "hot_lead"
}
]
},
"renameOutput": true
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "baa145e4-173e-404f-b468-085a6fecffa8",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.intent }}",
"rightValue": "warm_lead"
}
]
},
"renameOutput": true
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e47a1d18-dac3-41dd-8317-baf7deaa17ca",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.intent }}",
"rightValue": "support"
}
]
},
"renameOutput": true
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e7d23f7a-f7c2-463c-9a72-cd7bf2682235",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.intent }}",
"rightValue": "needs_qualification"
}
]
},
"renameOutput": true
},
{
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "acff1980-6f84-4444-a47d-7dd30b44b854",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.intent }}",
"rightValue": "spam"
}
]
},
"renameOutput": true
}
]
},
"options": {
"allMatchingOutputs": false
}
},
"typeVersion": 3.2
},
{
"id": "cf872e56-6064-4480-a709-0b35f6b0aefe",
"name": "\ud83d\udcac Hot Lead Reply",
"type": "n8n-nodes-base.code",
"position": [
1024,
240
],
"parameters": {
"jsCode": "const name = $json.name || 'there';\n\nconst replyMessage = `\ud83c\udf89 Excellent, ${name}!\n\nThank you for the detailed information. Our sales team is reviewing your requirements right now.\n\n\ud83d\udcc5 *Next Steps:*\nI've reserved a priority time slot for you. You'll receive a Google Meet link in the next message.\n\n\u23f0 *Response Time:* Within 30 minutes\n\nLet's get you set up! \ud83d\ude80`;\n\nreturn {\n json: {\n ...($json),\n autoReply: replyMessage,\n shouldReply: true\n }\n};"
},
"typeVersion": 2
},
{
"id": "57dc303c-58fd-4843-8713-a0723d08f4ef",
"name": "\ud83d\udcac Warm Lead Reply",
"type": "n8n-nodes-base.code",
"position": [
1040,
544
],
"parameters": {
"jsCode": "const name = $json.name || 'there';\n\nconst replyMessage = `\ud83d\udc4b Thanks for your interest, ${name}!\n\nHere's how we can help:\n\n\u2728 *What We Offer:*\n\u2022 Custom WhatsApp automation for your industry\n\u2022 Scalable from 10 to 10,000 users\n\u2022 24/7 technical support\n\u2022 Free 14-day trial\n\n\ud83d\udd17 *Learn More:*\nWebsite: [your-site.com]\nCase studies: [your-site.com/cases]\n\n\ud83d\udcac A team member will follow up with you shortly!\n\nFeel free to ask any questions here.`;\n\nreturn {\n json: {\n ...($json),\n autoReply: replyMessage,\n shouldReply: true\n }\n};"
},
"typeVersion": 2
},
{
"id": "92e58df8-3373-4903-af87-a0997a3b14fb",
"name": "\ud83d\udcac Support Reply",
"type": "n8n-nodes-base.code",
"position": [
1024,
848
],
"parameters": {
"jsCode": "const name = $json.name || 'there';\nconst ticketId = ($json.messageId || 'UNKNOWN').substring(0, 8).toUpperCase();\nconst issuePreview = ($json.messageBody || 'No description').substring(0, 50);\n\nconst replyMessage = `Hello ${name},\n\nThank you for reaching out. We've logged your support request.\n\n\ud83c\udfab *Ticket:* #${ticketId}\n\ud83d\udccb *Issue:* ${issuePreview}...\n\u23f0 *SLA:* Response within 2 hours (Mon-Fri, 9AM-6PM)\n\nOur support team is on it! \ud83d\udcaa\n\n\ud83d\udea8 *Urgent?* Call: +1-XXX-XXX-XXXX`;\n\nreturn {\n json: {\n ...($json),\n autoReply: replyMessage,\n shouldReply: true\n }\n};"
},
"typeVersion": 2
},
{
"id": "663e1710-f9ae-44c5-8535-e673987ac3b5",
"name": "\ud83e\udde0 Smart Qualification Reply",
"type": "n8n-nodes-base.code",
"position": [
1040,
1184
],
"parameters": {
"jsCode": "// For needs_qualification - use the AI-generated nextQuestion\nconst name = $json.name || 'there';\nlet qualifyMessage = $json.nextQuestion || '';\n\n// If AI didn't generate a question, use smart defaults\nif (!qualifyMessage) {\n const msg = ($json.messageBody || '').toLowerCase().trim();\n \n if (['hi', 'hello', 'hey', 'hii', 'hiii', 'good morning', 'good evening', 'good afternoon'].some(g => msg.startsWith(g))) {\n qualifyMessage = `Hi ${name}! \ud83d\udc4b Thanks for reaching out. How can I help you today? Are you looking for:\\n\\n1\ufe0f\u20e3 WhatsApp automation for your business\\n2\ufe0f\u20e3 Pricing information\\n3\ufe0f\u20e3 Technical support\\n\\nJust let me know! \ud83d\ude0a`;\n } else if (msg.includes('price') || msg.includes('pricing') || msg.includes('cost') || msg.includes('how much')) {\n qualifyMessage = `I'd love to help with pricing, ${name}! \ud83d\udcb0\\n\\nTo give you the most accurate quote, could you tell me:\\n\\n\ud83d\udccc What industry is your business in?\\n\ud83d\udccc How many team members would use it?\\n\\nThis helps me recommend the right plan for you! \ud83d\ude0a`;\n } else if (msg.includes('help') || msg.includes('assist')) {\n qualifyMessage = `Of course, ${name}! I'm here to help \ud83d\ude0a\\n\\nCould you tell me a bit more about what you're looking for? For example:\\n\\n\u2022 Are you exploring our product for the first time?\\n\u2022 Do you have a specific business challenge to solve?\\n\u2022 Or do you need help with an existing account?`;\n } else {\n qualifyMessage = `Thanks for your message, ${name}! \ud83d\ude0a\\n\\nI'd love to help you better. Could you tell me a bit more about what you're looking for and what your business does?`;\n }\n}\n\nreturn {\n json: {\n ...($json),\n autoReply: qualifyMessage,\n shouldReply: true,\n requiresFollowUp: true\n }\n};"
},
"typeVersion": 2
},
{
"id": "4b26367d-1454-410b-a00f-ccc4cd617041",
"name": "\ud83d\udcca Log Hot Lead",
"type": "n8n-nodes-base.googleSheets",
"position": [
1280,
240
],
"parameters": {
"columns": {
"value": {
"From": "={{ $json.from }}",
"Name": "={{ $json.name }}",
"Intent": "={{ $json.intent }}",
"Reason": "={{ $json.reason }}",
"Status": "={{ $json.intent === 'hot_lead' ? 'URGENT' : $json.intent === 'warm_lead' ? 'Follow Up' : $json.intent === 'support' ? 'Support Ticket' : $json.intent === 'needs_qualification' ? 'Qualifying' : 'Ignored' }}",
"timestamp": "={{ $json.timestamp }}",
"Confidence": "={{ $json.confidence }}",
"message id": "={{ $json.messageId }}",
"recived at": "={{ $json.receivedAt }}",
"Message Type": "={{ $json.messageType }}",
"Missing Info": "={{ $json.missingInfo.join(', ') }}",
"message body": "={{ $json.messageBody }}",
"Question Asked": "={{ $json.nextQuestion }}"
},
"schema": [
{
"id": "From",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "From",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Name",
"type": "string",
"display": true,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "message body",
"type": "string",
"display": true,
"required": false,
"displayName": "message body",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "message id",
"type": "string",
"display": true,
"required": false,
"displayName": "message id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "recived at",
"type": "string",
"display": true,
"required": false,
"displayName": "recived at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Intent",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Intent",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Confidence",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Confidence",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Reason",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Reason",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message Type",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Message Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Missing Info",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Missing Info",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Question Asked",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Question Asked",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"From"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
"cachedResultName": "Leads"
}
},
"typeVersion": 4.6
},
{
"id": "1575f70e-2c71-4558-9f56-5b45ef1919c4",
"name": "\ud83d\udcca Log Warm Lead",
"type": "n8n-nodes-base.googleSheets",
"position": [
1280,
544
],
"parameters": {
"columns": {
"value": {
"From": "={{ $json.from }}",
"Name": "={{ $json.name }}",
"Intent": "={{ $json.intent }}",
"Reason": "={{ $json.reason }}",
"Status": "Follow Up",
"timestamp": "={{ $json.timestamp }}",
"Confidence": "={{ $json.confidence }}",
"message id": "={{ $json.messageId }}",
"recived at": "={{ $json.receivedAt }}",
"Message Type": "={{ $json.messageType }}",
"Missing Info": "={{ $json.missingInfo.join(', ') }}",
"message body": "={{ $json.messageBody }}",
"Question Asked": "={{ $json.nextQuestion }}"
},
"schema": [
{
"id": "From",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "From",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Name",
"type": "string",
"display": true,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "message body",
"type": "string",
"display": true,
"required": false,
"displayName": "message body",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "message id",
"type": "string",
"display": true,
"required": false,
"displayName": "message id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "recived at",
"type": "string",
"display": true,
"required": false,
"displayName": "recived at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Intent",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Intent",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Confidence",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Confidence",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Reason",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Reason",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message Type",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Message Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Missing Info",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Missing Info",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Question Asked",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Question Asked",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"From"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
"cachedResultName": "Leads"
}
},
"typeVersion": 4.6
},
{
"id": "2429189f-2536-4ffc-b8a0-445188b2ac7e",
"name": "\ud83d\udcca Log Support",
"type": "n8n-nodes-base.googleSheets",
"position": [
1264,
848
],
"parameters": {
"columns": {
"value": {
"From": "={{ $json.from }}",
"Name": "={{ $json.name }}",
"Intent": "={{ $json.intent }}",
"Reason": "={{ $json.reason }}",
"Status": "Support Ticket",
"timestamp": "={{ $json.timestamp }}",
"Confidence": "={{ $json.confidence }}",
"message id": "={{ $json.messageId }}",
"recived at": "={{ $json.receivedAt }}",
"Message Type": "={{ $json.messageType }}",
"Missing Info": "={{ $json.missingInfo.join(', ') }}",
"message body": "={{ $json.messageBody }}",
"Question Asked": "={{ $json.nextQuestion }}"
},
"schema": [
{
"id": "From",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "From",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Name",
"type": "string",
"display": true,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "message body",
"type": "string",
"display": true,
"required": false,
"displayName": "message body",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "message id",
"type": "string",
"display": true,
"required": false,
"displayName": "message id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "recived at",
"type": "string",
"display": true,
"required": false,
"displayName": "recived at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Intent",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Intent",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Confidence",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Confidence",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Reason",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Reason",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message Type",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Message Type",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Missing Info",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Missing Info",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Question Asked",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Question Asked",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"From"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=0",
"cachedResultName": "Sheet1"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "YOUR_GOOGLE_SHEET_ID",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
"cachedResultName": "Leads"
}
},
"typeVersion": 4.6
},
{
"id": "73ef34e9-abd8-42af-bf67-9397e07ab590",
"name": "\ud83d\udcc5 Book Meeting (Hot)",
"type": "n8n-nodes-base.googleCalendar",
"position": [
1536,
240
],
"parameters": {
"end": "={{ $now.plus(3, 'hours').toISO() }}",
"start": "={{ $now.plus(2, 'hours').toISO() }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "Your Calendar"
},
"additionalFields": {
"description": "=Intent: {{ $json.Intent }}\nReason: {{ $json.Reason }}\nMessage: {{ $json['message body'] }}\nPhone: {{ $json.From }}",
"conferenceDataUi": {
"conferenceDataValues": {
"conferenceSolution": "={{ $json.Name }}"
}
}
},
"useDefaultReminders": false
},
"typeVersion": 1.2
},
{
"id": "d841bc4d-8899-4900-8d41-da02a603c192",
"name": "\ud83d\udcc5 Book Meeting (Warm)",
"type": "n8n-nodes-base.googleCalendar",
"position": [
1536,
544
],
"parameters": {
"end": "={{ $now.plus(5, 'hours').toISO() }}",
"start": "={{ $now.plus(4, 'hours').toISO() }}",
"calendar": {
"__rl": true,
"mode": "list",
"value": "user@example.com",
"cachedResultName": "Your Calendar"
},
"additionalFields": {
"description": "=Intent: {{ $json.Intent }}\nReason: {{ $json.Reason }}\nMessage: {{ $json['message body'] }}\nPhone: {{ $json.From }}",
"conferenceDataUi": {
"conferenceDataValues": {
"conferenceSolution": "={{ $json.Name }}"
}
}
},
"useDefaultReminders": false
},
"typeVersion": 1.2
},
{
"id": "df2d4acc-320b-427b-a5eb-ef212eeb8642",
"name": "\ud83d\udcf1 Send Meeting (Hot)",
"type": "n8n-nodes-base.whatsApp",
"position": [
1744,
240
],
"parameters": {
"textBody": "=\ud83d\udcc5 *Your meeting is confirmed!*\n\nHi {{ $('\ud83c\udfaf Extract Classification').item.json.name }},\n\n\ud83d\udd25 *Priority Booking*\n\n*Join the call:*\n{{ $json.hangoutLink }}\n\n*Scheduled for:*\n{{ $now.plus(2, 'hours').toFormat('MMMM dd, yyyy at hh:mm a') }}\n\nOur team is excited to discuss your requirements! \ud83d\ude80\n\n_Reply \"RESCHEDULE\" if this time doesn't work._",
"operation": "send",
"phoneNumberId": "YOUR_PHONE_NUMBER_ID",
"additionalFields": {},
"recipientPhoneNumber": "={{ $('\ud83c\udfaf Extract Classification').item.json.from }}"
},
"typeVersion": 1.1
},
{
"id": "3487d76d-3869-43fd-9d73-d53e624570c7",
"name": "\ud83d\udcf1 Send Meeting (Warm)",
"type": "n8n-nodes-base.whatsApp",
"position": [
1760,
544
],
"parameters": {
"textBody": "=\ud83d\udcc5 *Meeting Invitation*\n\nHi {{ $('\ud83c\udfaf Extract Classification').item.json.name }},\n\nWe'd love to show you how we can help your business!\n\n*Join the call:*\n{{ $json.hangoutLink }}\n\n*Scheduled for:*\n{{ $now.plus(4, 'hours').toFormat('MMMM dd, yyyy at hh:mm a') }}\n\nLooking forward to chatting! \ud83d\ude0a\n\n_Reply \"RESCHEDULE\" if this time doesn't work._",
"operation": "send",
"phoneNumberId": "YOUR_PHONE_NUMBER_ID",
"additionalFields": {},
"recipientPhoneNumber": "={{ $('\ud83c\udfaf Extract Classification').item.json.from }}"
},
"typeVersion": 1.1
},
{
"id": "0bf7972f-c4b6-4c7d-b16a-c8f50e2a2e0f",
"name": "\ud83d\udcf1 Send Support Reply",
"type": "n8n-nodes-base.whatsApp",
"position": [
1504,
848
],
"parameters": {
"textBody": "={{ $('\ud83d\udcac Support Reply').item.json.autoReply }}",
"operation": "send",
"phoneNumberId": "YOUR_PHONE_NUMBER_ID",
"additionalFields": {},
"recipientPhoneNumber": "={{ $('\ud83c\udfaf Extract Classification').item.json.from }}"
},
"typeVersion": 1.1
},
{
"id": "781ca17f-0ece-4f8f-988a-a0d15eed09fa",
"name": "\ud83d\udcf1 Send Qualifying Question",
"type": "n8n-nodes-base.whatsApp",
"position": [
1312,
1184
],
"parameters": {
"textBody": "={{ $('\ud83e\udde0 Smart Qualification Reply').item.json.autoReply }}",
"operation": "send",
"phoneNumberId": "YOUR_PHONE_NUMBER_ID",
"additionalFields": {},
"recipientPhoneNumber": "={{ $('\ud83c\udfaf Extract Classification').item.json.from }}"
},
"typeVersion": 1.1
},
{
"id": "9a25bf7e-7b7f-4ced-8bf6-0399b6ab19fa",
"name": "WhatsApp Trigger",
"type": "n8n-nodes-base.whatsAppTrigger",
"position": [
-224,
752
],
"parameters": {
"options": {},
"updates": [
"messages"
]
},
"typeVersion": 1
},
{
"id": "2bf8bb35-71d6-472e-93e0-d28447b61f00",
"name": "Simple Memory",
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"position": [
352,
976
],
"parameters": {
"sessionKey": "=={{ $json.waId }}",
"sessionIdType": "customKey",
"contextWindowLength": 20
},
"typeVersion": 1.3
},
{
"id": "a40b2a98-3117-4bb3-9205-3b95e526a3e3",
"name": "Ollama Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
"position": [
224,
976
],
"parameters": {
"model": "minimax-m2:cloud",
"options": {}
},
"typeVersion": 1
},
{
"id": "486ef98e-c024-4fcf-a17a-676557641b8f",
"name": "Sticky Note - Main Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1024,
-96
],
"parameters": {
"width": 680,
"height": 860,
"content": "## Auto-respond and classify WhatsApp leads with AI and log to Google Sheets\n\nAutomatically classify incoming WhatsApp messages using AI, route them by intent, log every interaction to Google Sheets, book Google Calendar meetings, and send smart auto-replies \u2014 all without lifting a finger.\n\n### How it works\n1. **WhatsApp Trigger** receives incoming messages and filters out bot/status messages to prevent infinite loops.\n2. **AI Agent** (Ollama or any LLM) classifies each message into: \ud83d\udd25 Hot Lead, \ud83c\udf21\ufe0f Warm Lead, \ud83d\udee0\ufe0f Support, or \u2753 Needs Qualification.\n3. **Smart Router** sends each intent down a dedicated path.\n4. **Hot & Warm Leads** get logged to Google Sheets, a Google Calendar meeting is auto-booked with a Meet link, and the customer receives the booking via WhatsApp.\n5. **Support** tickets are logged and a confirmation reply is sent.\n6. **Needs Qualification** triggers a smart follow-up question via WhatsApp. Conversation memory ensures the AI re-classifies correctly when the user replies with more context.\n\n### Setup steps\n1. Connect your **WhatsApp Business API** credentials (Meta Cloud API).\n2. Connect **Google Sheets** OAuth and set your spreadsheet ID in all three logging nodes.\n3. Connect **Google Calendar** OAuth and select your calendar in both booking nodes.\n4. Configure your **LLM** endpoint (Ollama, OpenAI, or any supported model).\n5. Update `BOT_NUMBERS` in the Parse node and `phoneNumberId` in all WhatsApp Send nodes to match your number.\n6. Send a test message to verify the full flow.\n\n### Customization\n- Swap the AI model for OpenAI, Anthropic, or any n8n-supported LLM.\n- Edit auto-reply templates in each Reply code node to match your brand.\n- Adjust meeting booking times (Hot = 2hrs, Warm = 4hrs).\n- Add Slack or email notifications by branching from the Sheet nodes."
},
"typeVersion": 1
},
{
"id": "80ba5bf8-26e0-4da4-bd64-361489771a2e",
"name": "Sticky Note - Incoming",
"type": "n8n-nodes-base.stickyNote",
"position": [
-240,
640
],
"parameters": {
"color": 6,
"width": 384,
"height": 212,
"content": "## \ud83d\udce5 Incoming Message\nWhatsApp Trigger receives and parses incoming messages"
},
"typeVersion": 1
},
{
"id": "2b0afded-214d-480c-b853-ba23e604002c",
"name": "Sticky Note - AI",
"type": "n8n-nodes-base.stickyNote",
"position": [
192,
640
],
"parameters": {
"color": 6,
"width": 448,
"height": 196,
"content": "## \ud83e\udd16 AI Classification + Extraction\nAI classifies intent, then robust parser extracts the result"
},
"typeVersion": 1
},
{
"id": "6f0aada9-17f8-4914-92b8-66eccf2a2071",
"name": "Sticky Note - Router",
"type": "n8n-nodes-base.stickyNote",
"position": [
688,
624
],
"parameters": {
"color": 6,
"width": 280,
"height": 396,
"content": "## \ud83d\udd00 Intent Routing\nRoutes to the correct path based on AI classification"
},
"typeVersion": 1
},
{
"id": "98e417e7-eb5d-4165-81e9-3ab0427b3511",
"name": "Sticky Note - Hot",
"type": "n8n-nodes-base.stickyNote",
"position": [
992,
144
],
"parameters": {
"color": 6,
"width": 1024,
"height": 240,
"content": "## \ud83d\udd25 Hot Lead Path\nReply \u2192 Log to Sheets \u2192 Book meeting \u2192 Send Google Meet invite"
},
"typeVersion": 1
},
{
"id": "eaf2a4ed-9999-428e-be10-617c3878162b",
"name": "Sticky Note - Warm",
"type": "n8n-nodes-base.stickyNote",
"position": [
1008,
464
],
"parameters": {
"color": 6,
"width": 1008,
"height": 240,
"content": "## \ud83c\udf21\ufe0f Warm Lead Path\nReply \u2192 Log to Sheets \u2192 Book meeting \u2192 Send Google Meet invite"
},
"typeVersion": 1
},
{
"id": "b9f1c25b-a26d-4f86-a389-244f81e2d6d6",
"name": "Sticky Note - Support",
"type": "n8n-nodes-base.stickyNote",
"position": [
1008,
752
],
"parameters": {
"color": 6,
"width": 1024,
"height": 256,
"content": "## \ud83d\udee0\ufe0f Support Path\nReply \u2192 Log to Sheets \u2192 Send ticket confirmation via WhatsApp"
},
"typeVersion": 1
},
{
"id": "a9454d6c-a33d-4cdc-ae12-c796f31e2a88",
"name": "Sticky Note - Qualify",
"type": "n8n-nodes-base.stickyNote",
"position": [
1008,
1088
],
"parameters": {
"color": 6,
"width": 488,
"height": 256,
"content": "## \u2753 Qualification Path\nSmart follow-up question via WhatsApp. Memory ensures re-classification on reply."
},
"typeVersion": 1
},
{
"id": "b7761232-2733-4579-aaff-ffd1a0c0f139",
"name": "Sticky Note - Warning",
"type": "n8n-nodes-base.stickyNote",
"position": [
-496,
992
],
"parameters": {
"color": 3,
"width": 376,
"content": "## \u26a0\ufe0f Update Bot Number\nChange the `BOT_NUMBERS` array in the Parse node to match your WhatsApp Business phone number ID. This prevents infinite loops from the bot replying to itself."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "63d1fe57-de3f-4a9a-9a3c-34a8c1e17930",
"connections": {
"Simple Memory": {
"ai_memory": [
[
{
"node": "\ud83e\udd16 AI Intent Classifier",
"type": "ai_memory",
"index": 0
}
]
]
},
"WhatsApp Trigger": {
"main": [
[
{
"node": "\ud83d\udd27 Parse WhatsApp Message",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcca Log Support": {
"main": [
[
{
"node": "\ud83d\udcf1 Send Support Reply",
"type": "main",
"index": 0
}
]
]
},
"Ollama Chat Model": {
"ai_languageModel": [
[
{
"node": "\ud83e\udd16 AI Intent Classifier",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"\ud83d\udcca Log Hot Lead": {
"main": [
[
{
"node": "\ud83d\udcc5 Book Meeting (Hot)",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcac Support Reply": {
"main": [
[
{
"node": "\ud83d\udcca Log Support",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcca Log Warm Lead": {
"main": [
[
{
"node": "\ud83d\udcc5 Book Meeting (Warm)",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcac Hot Lead Reply": {
"main": [
[
{
"node": "\ud83d\udcca Log Hot Lead",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcac Warm Lead Reply": {
"main": [
[
{
"node": "\ud83d\udcca Log Warm Lead",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd00 Route by Intent": {
"main": [
[
{
"node": "\ud83d\udcac Hot Lead Reply",
"type": "main",
"index": 0
}
],
[
{
"node": "\ud83d\udcac Warm Lead Reply",
"type": "main",
"index": 0
}
],
[
{
"node": "\ud83d\udcac Support Reply",
"type": "main",
"index": 0
}
],
[
{
"node": "\ud83e\udde0 Smart Qualification Reply",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcc5 Book Meeting (Hot)": {
"main": [
[
{
"node": "\ud83d\udcf1 Send Meeting (Hot)",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udcc5 Book Meeting (Warm)": {
"main": [
[
{
"node": "\ud83d\udcf1 Send Meeting (Warm)",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udd16 AI Intent Classifier": {
"main": [
[
{
"node": "\ud83c\udfaf Extract Classification",
"type": "main",
"index": 0
}
]
]
},
"\ud83c\udfaf Extract Classification": {
"main": [
[
{
"node": "\ud83d\udd00 Route by Intent",
"type": "main",
"index": 0
}
]
]
},
"\ud83d\udd27 Parse WhatsApp Message": {
"main": [
[
{
"node": "\ud83e\udd16 AI Intent Classifier",
"type": "main",
"index": 0
}
]
]
},
"\ud83e\udde0 Smart Qualification Reply": {
"main": [
[
{
"node": "\ud83d\udcf1 Send Qualifying Question",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow is built for sales teams, agencies, and small businesses that receive inbound leads via WhatsApp and want to automate their first response, lead qualification, and CRM logging — without missing a single message.
Source: https://n8n.io/workflows/13450/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
WhatsApp AI Assistant for Clinic Appointment Booking Automate your entire appointment lifecycle with an intelligent AI assistant that lives on WhatsApp. This workflow empowers any clinic or independen
This workflow implements a WhatsApp-based virtual restaurant assistant that automates customer interaction from the first message to post-dining follow-up.
Streamline restaurant reservations on WhatsApp
Automate_Product_Training___Customer_Support_via_WhatsApp__GPT_4___Google_Sheets. Uses whatsApp, whatsAppTrigger, httpRequest, googleSheets. Event-driven trigger; 21 nodes.
5-Automate_Product_Training___Customer_Support_via_WhatsApp__GPT_4___Google_Sheets. Uses whatsApp, whatsAppTrigger, httpRequest, googleSheets. Event-driven trigger; 21 nodes.