This workflow corresponds to n8n.io template #15044 — we link there as the canonical source.
This workflow follows the Chainllm → Google Sheets 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": "IxqVFDv8v4qKO24G",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "WhatsApp AI CRM \u2014 Whapi + Ollama",
"tags": [],
"nodes": [
{
"id": "e5a09e9a-3413-456d-85dd-013aced66a04",
"name": "Sticky Note \u2014 Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
656,
240
],
"parameters": {
"color": 7,
"width": 520,
"height": 620,
"content": "## \ud83d\udcf1 WhatsApp AI CRM \u2014 Whapi + Ollama\n\nAutomatically respond to WhatsApp messages with a **local AI model (Ollama)**, log every conversation in **Google Sheets**, and capture leads \u2014 all without paying for cloud LLMs.\n\n**Use cases:** Real estate, service businesses, agencies, appointment booking, customer support.\n\n---\n### \u2699\ufe0f Setup Checklist\n1. Connect **Google Sheets OAuth2** credentials\n2. Replace the Google Sheet ID in all Sheets nodes\n3. Add your **Whapi API token** to the *Send Reply* node\n4. Add your **Ollama** credentials (base URL of your Ollama server)\n5. Point your **Whapi webhook** to this workflow's webhook URL\n6. Create the two sheets: `History` and `Leads` (see sticky notes below)\n7. \u270f\ufe0f Edit the system prompt in **Build AI Prompt** to match your business"
},
"typeVersion": 1
},
{
"id": "403b4472-5305-4c9a-9eb9-db2be0fae157",
"name": "Sticky Note \u2014 Sheets Setup",
"type": "n8n-nodes-base.stickyNote",
"position": [
672,
896
],
"parameters": {
"color": 5,
"width": 420,
"height": 280,
"content": "### \ud83d\udcca Google Sheet Structure\n\nCreate **one Google Sheet** with two tabs:\n\n**Tab 1 \u2014 `History`** (conversation log)\n| Phone | Name | Role | Message | Timestamp |\n\n**Tab 2 \u2014 `Leads`** (CRM)\n| Phone | Name | Last Message | Last Contact | Status |\n\n> Copy the Sheet ID from the URL:\n> `docs.google.com/spreadsheets/d/**[SHEET_ID]**/edit`\n> Paste it into all 4 Google Sheets nodes."
},
"typeVersion": 1
},
{
"id": "006d9f66-657a-4a9c-8200-ff43d164effa",
"name": "Sticky Note \u2014 Webhook",
"type": "n8n-nodes-base.stickyNote",
"position": [
1264,
480
],
"parameters": {
"color": 4,
"width": 300,
"height": 232,
"content": "### \ud83d\udd17 Step 1 \u2014 Receive WhatsApp Message\nThis webhook receives all incoming events from **Whapi.cloud**.\n\n**To connect:**\n1. Copy this node's **Production URL**\n2. In your Whapi dashboard \u2192 Settings \u2192 Webhook URL \u2192 paste it\n3. Enable the `messages` event type"
},
"typeVersion": 1
},
{
"id": "9eab2a42-27eb-4489-8e1b-142fd3e16058",
"name": "Sticky Note \u2014 Filter",
"type": "n8n-nodes-base.stickyNote",
"position": [
1728,
432
],
"parameters": {
"color": 4,
"width": 260,
"height": 254,
"content": "### \ud83d\udd0d Step 2 \u2014 Filter\nOnly processes **inbound text messages** from customers.\n\nIgnores:\n- Messages sent by you (fromMe = true)\n- Media, voice, or sticker messages\n- Empty payloads"
},
"typeVersion": 1
},
{
"id": "b56ee197-b2e6-43e6-af9b-5bbcb6a2ce0b",
"name": "Sticky Note \u2014 AI",
"type": "n8n-nodes-base.stickyNote",
"position": [
2048,
288
],
"parameters": {
"color": 3,
"width": 420,
"height": 292,
"content": "### \ud83e\udde0 Step 3 \u2014 Build Prompt + AI Reply\nFetches past conversation history for this contact, then builds a full prompt with:\n- System instructions (your business persona)\n- Conversation history\n- The new message\n\n\u270f\ufe0f **Customise the system prompt** inside the *Build AI Prompt* code node to match your industry (real estate, bookings, support, etc.)\n\nThe **Ollama** node runs a local model \u2014 default is `gemma3:1b`. Change it to `llama3`, `mistral`, or any model you have pulled."
},
"typeVersion": 1
},
{
"id": "e74e29da-2404-41fb-b666-b27a0829690f",
"name": "Sticky Note \u2014 CRM & Send",
"type": "n8n-nodes-base.stickyNote",
"position": [
2768,
864
],
"parameters": {
"color": 6,
"width": 420,
"height": 278,
"content": "### \ud83d\udcbe Step 4 \u2014 Log + CRM + Send\n- **Log User Message** \u2014 appends the customer message to History sheet\n- **Log AI Reply** \u2014 appends the AI response to History sheet\n- **Upsert Lead** \u2014 creates or updates the lead record in the Leads sheet (matched by phone number)\n- **Send Reply via Whapi** \u2014 delivers the AI reply back to the customer on WhatsApp\n\n> \u26a0\ufe0f Replace the `Bearer` token in the *Send Reply* HTTP node with your Whapi API key."
},
"typeVersion": 1
},
{
"id": "1c487dac-cd45-4ba2-93a8-dbff7fef4abc",
"name": "Receive WhatsApp Message",
"type": "n8n-nodes-base.webhook",
"position": [
1360,
784
],
"parameters": {
"path": "whapi-crm",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2
},
{
"id": "fa7ebcb1-7bcd-4185-b85b-884050e3eb11",
"name": "Extract Message Data",
"type": "n8n-nodes-base.code",
"position": [
1600,
784
],
"parameters": {
"jsCode": "// Extract data from Whapi.cloud webhook payload\nconst body = $input.first().json.body || $input.first().json;\n\n// Whapi sends a messages array\nconst messages = body.messages || [];\n\nif (messages.length === 0) {\n return [{ json: { skip: true, phone: '', name: '', messageText: '', fromMe: true, messageType: 'unknown' } }];\n}\n\nconst msg = messages[0];\n\n// Extract phone number (remove @s.whatsapp.net if present)\nconst rawPhone = msg.chatId || msg.from || '';\nconst phone = rawPhone.replace('@s.whatsapp.net', '').replace('@c.us', '');\n\nreturn [{\n json: {\n skip: false,\n phone: phone,\n name: msg.pushName || msg.senderName || 'Unknown',\n messageText: msg.body || (msg.text && msg.text.body) || '',\n fromMe: msg.fromMe === true,\n messageType: msg.type || 'unknown',\n messageId: msg.id || '',\n timestamp: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "209fa1ad-637b-44d9-9093-9323bb0e191e",
"name": "Is Text From Customer?",
"type": "n8n-nodes-base.if",
"position": [
1840,
784
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "cond-1",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.fromMe }}",
"rightValue": false
},
{
"id": "cond-2",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.messageType }}",
"rightValue": "text"
},
{
"id": "cond-3",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json.skip }}",
"rightValue": false
}
]
}
},
"typeVersion": 2
},
{
"id": "05357c62-23f3-4e14-999c-a3d5a8e22ecc",
"name": "Read Conversation History",
"type": "n8n-nodes-base.googleSheets",
"position": [
2080,
688
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $('Is Text From Customer?').first().json.phone }}",
"lookupColumn": "Phone"
}
]
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "History"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"typeVersion": 4.5,
"alwaysOutputData": true
},
{
"id": "f09be5f9-0b71-440c-94cc-c84c3652d7a2",
"name": "Build AI Prompt",
"type": "n8n-nodes-base.code",
"position": [
2320,
688
],
"parameters": {
"jsCode": "// Get data from earlier nodes\nconst msgData = $('Is Text From Customer?').first().json;\nconst historyRows = $input.all();\n\n// Build conversation history string from Google Sheets rows\nlet historyText = '';\nfor (const row of historyRows) {\n if (row.json.Role && row.json.Message) {\n const label = row.json.Role === 'user' ? msgData.name : 'Assistant';\n historyText += `${label}: ${row.json.Message}\\n`;\n }\n}\n\n// \u270f\ufe0f CUSTOMISE THIS SYSTEM PROMPT for your business\nconst systemPrompt = `You are a friendly and professional real estate assistant available 24/7. Your job is to:\n- Welcome potential buyers/renters warmly\n- Understand their property needs (location, budget, type, size)\n- Answer questions about available properties\n- Collect their contact info naturally during conversation\n- Offer to schedule property viewings\n- Keep responses short and conversational (2-4 sentences max)\n- Always reply in the SAME LANGUAGE the customer writes in\n\nCustomer name: ${msgData.name}\nCustomer phone: ${msgData.phone}`;\n\nconst fullPrompt = `${systemPrompt}\n\n--- Conversation History ---\n${historyText || '(This is the first message)'}---\n\n${msgData.name}: ${msgData.messageText}\nAssistant:`;\n\nreturn [{\n json: {\n ...msgData,\n prompt: fullPrompt\n }\n}];"
},
"typeVersion": 2
},
{
"id": "f7d2e632-97d2-4710-92db-66908a6ff896",
"name": "Generate AI Reply",
"type": "@n8n/n8n-nodes-langchain.chainLlm",
"position": [
2512,
608
],
"parameters": {
"text": "={{ $json.prompt }}",
"promptType": "define"
},
"typeVersion": 1.4
},
{
"id": "680c228d-9727-4da1-a78e-e69473b249e0",
"name": "Ollama Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOllama",
"position": [
2576,
928
],
"parameters": {
"model": "gemma3:1b",
"options": {}
},
"typeVersion": 1
},
{
"id": "b83f682a-68a2-4a35-bd6c-0b14755d50b5",
"name": "Log User Message",
"type": "n8n-nodes-base.googleSheets",
"position": [
2800,
688
],
"parameters": {
"columns": {
"value": {},
"schema": [
{
"id": "Phone",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Name",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Role",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Role",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Timestamp",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "autoMapInputData",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "History"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "496cbb67-eef1-460c-aa6c-99ebe9c09422",
"name": "Log AI Reply",
"type": "n8n-nodes-base.googleSheets",
"position": [
3040,
688
],
"parameters": {
"columns": {
"value": {
"Name": "={{ $('Build AI Prompt').first().json.name }}",
"Role": "assistant",
"Phone": "={{ $('Build AI Prompt').first().json.phone }}",
"Message": "={{ $('Generate AI Reply').first().json.text }}",
"Timestamp": "={{ $now.toISO() }}"
},
"schema": [
{
"id": "Phone",
"type": "string",
"display": true,
"required": false,
"displayName": "Phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Name",
"type": "string",
"display": true,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Role",
"type": "string",
"display": true,
"required": false,
"displayName": "Role",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Message",
"type": "string",
"display": true,
"required": false,
"displayName": "Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Timestamp",
"type": "string",
"display": true,
"required": false,
"displayName": "Timestamp",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "History"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "467eb3c8-7b2d-48df-87dc-f74c2d5ac11e",
"name": "Upsert Lead in CRM",
"type": "n8n-nodes-base.googleSheets",
"position": [
3280,
688
],
"parameters": {
"columns": {
"value": {
"Name": "={{ $('Build AI Prompt').first().json.name }}",
"Phone": "={{ $('Build AI Prompt').first().json.phone }}",
"Status": "New Lead",
"Last Contact": "={{ $now.toISO() }}",
"Last Message": "={{ $('Build AI Prompt').first().json.messageText }}"
},
"schema": [
{
"id": "Phone",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Name",
"type": "string",
"display": true,
"required": false,
"displayName": "Name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Last Message",
"type": "string",
"display": true,
"required": false,
"displayName": "Last Message",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Last Contact",
"type": "string",
"display": true,
"required": false,
"displayName": "Last Contact",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"Phone"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "appendOrUpdate",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Leads"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"typeVersion": 4.5
},
{
"id": "593de6dc-af8f-4238-a663-50917a2054ce",
"name": "Send Reply via Whapi",
"type": "n8n-nodes-base.httpRequest",
"position": [
3520,
688
],
"parameters": {
"url": "https://gate.whapi.cloud/messages/text",
"method": "POST",
"options": {},
"jsonBody": "={{ JSON.stringify({ to: $('Build AI Prompt').first().json.phone, body: $('Generate AI Reply').first().json.text }) }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer YOUR_TOKEN_HERE"
},
{
"name": "Content-Type",
"value": "application/json"
}
]
}
},
"typeVersion": 4.2
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "0823fded-22d8-4724-9c0f-92ece36b3b61",
"connections": {
"Log AI Reply": {
"main": [
[
{
"node": "Upsert Lead in CRM",
"type": "main",
"index": 0
}
]
]
},
"Ollama Model": {
"ai_languageModel": [
[
{
"node": "Generate AI Reply",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Build AI Prompt": {
"main": [
[
{
"node": "Generate AI Reply",
"type": "main",
"index": 0
}
]
]
},
"Log User Message": {
"main": [
[
{
"node": "Log AI Reply",
"type": "main",
"index": 0
}
]
]
},
"Generate AI Reply": {
"main": [
[
{
"node": "Log User Message",
"type": "main",
"index": 0
}
]
]
},
"Upsert Lead in CRM": {
"main": [
[
{
"node": "Send Reply via Whapi",
"type": "main",
"index": 0
}
]
]
},
"Extract Message Data": {
"main": [
[
{
"node": "Is Text From Customer?",
"type": "main",
"index": 0
}
]
]
},
"Is Text From Customer?": {
"main": [
[
{
"node": "Read Conversation History",
"type": "main",
"index": 0
}
]
]
},
"Receive WhatsApp Message": {
"main": [
[
{
"node": "Extract Message Data",
"type": "main",
"index": 0
}
]
]
},
"Read Conversation History": {
"main": [
[
{
"node": "Build AI Prompt",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Receive WhatsApp messages via Whapi, generate AI replies with a local Ollama model, log conversations in Google Sheets, and auto-capture leads — all without touching a cloud LLM.
Source: https://n8n.io/workflows/15044/ — 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.
ANIS_HUB 1. Uses gmail, googleDrive, googleSheets, httpRequest. Webhook trigger; 89 nodes.
This n8n workflow orchestrates a powerful suite of AI Agents and automations to manage and optimize various aspects of an e-commerce operation, particularly for platforms like Shopify. It leverages La
Resume Screening & Behavioral Interviews with Gemini, Elevenlabs, & Notion ATS copy. Uses outputParserStructured, chainLlm, googleDrive, stickyNote. Webhook trigger; 67 nodes.
Candidate Engagement | Resume Screening | AI Voice Interviews | Applicant Insights
leads. Uses supabase, gmail, formTrigger, httpRequest. Webhook trigger; 62 nodes.