This workflow corresponds to n8n.io template #13722 — we link there as the canonical source.
This workflow follows the Google Sheets → HTTP Request 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 →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "ac1011ce-6d22-4dd7-89b1-4ed4e48fab02",
"name": "\ud83d\uddfa\ufe0f Architecture Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2800,
2848
],
"parameters": {
"width": 680,
"height": 788,
"content": "## \ud83d\uded2 Smart Shopping Assistant \u2013 WhatsApp Price Comparison Bot\n\n**3 independent pipelines \u2014 each does one job cleanly:**\n\n\ud83d\udce5 **PIPELINE A \u00b7 Intake**\nUser sends a list \u2192 OpenAI parses every item with qty/unit/category \u2192 saved to Google Sheets \u2192 confirmation sent\n\n\ud83d\udd0d **PIPELINE B \u00b7 Price Engine**\nUser sends *compare* \u2192 for each item: Serper searches live web prices \u2192 OpenAI picks best deal \u2192 full comparison card sent back\n\n\ud83d\udcec **PIPELINE C \u00b7 Alerts**\nSchedule runs daily 8AM \u2192 checks saved deals \u2192 sends personalised alert to any user with savings \u2265 10%\n\n---\n**Credentials:** WATI \u00b7 OpenAI Header Auth \u00b7 Serper Header Auth \u00b7 Google Sheets OAuth2\n**Serper:** free tier at serper.dev \u2013 2500 searches/month"
},
"typeVersion": 1
},
{
"id": "aca804c2-dea9-4191-9aa3-74385356d50d",
"name": "Pipeline A Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1840,
4000
],
"parameters": {
"color": 7,
"width": 444,
"height": 760,
"content": "Node: Wati Trigger1 & Intent Router1\nFunction: Captures incoming WhatsApp messages via Wati.Logic: The Switch node parses the {{ $json.text }} to determine what the user wants.\ncompare (Price check)\ndeals (View saved bargains)\nmylist (View shopping list)\nclear (Reset list)"
},
"typeVersion": 1
},
{
"id": "1f7a04e3-de69-44bc-9121-582d38630b56",
"name": "Pipeline B Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1264,
4048
],
"parameters": {
"color": 7,
"width": 1972,
"height": 480,
"content": "### \ud83d\udd0d Pipeline B \u00b7 Price Engine\n**Flow per item:**\n`Read List for Compare` \u2192 `Prepare Search Queries.`Serper \u2013 Search Prices`.`OpenAI \u2013 Extract Best Deal`.`Process Deal Result`.`Google Sheets \u2013 Save Deals`*(appends one row per item to Deals tab)*.`Aggregate Comparison Card`*(collects ALL items, calculates total, builds full card)*\u2193`Send a text message`\n*(sends full comparison card to user)*\n\n---\n\u26a0\ufe0f **Important:** n8n runs each item through the chain sequentially. The `Aggregate` node uses `$input.all()` to collect every item result before sending the final message."
},
"typeVersion": 1
},
{
"id": "9ececa7a-15f6-45a6-9d1a-e9a5eea9ba0a",
"name": "Pipeline C Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
1104,
4352
],
"parameters": {
"color": 7,
"width": 1108,
"height": 468,
"content": "### \ud83d\udcec Pipeline C \u00b7 Daily Deal Alerts\nRuns automatically every day at 8:00 AM\n**Flow:**\nSchedule Trigger \u2013 Daily 8AM Alerts`.`Google Sheets \u2013 Read Deals for Alert`*(reads entire Deals tab)*.`Build Alert Messages`*(filters: alertSent = No AND savingPct \u2265 10)**(groups by phone, picks top 5 deals per user)**(returns one output item per user)*.`WATI \u2013 Send Deal Alert`*(sends personalised message to each user's phone)*\n---\n\u26a0\ufe0f **To-do after import:**\n- `alertSent` is not automatically updated to `Yes` after sending.\nAdd a `Google Sheets \u2013 Update` node after `Send Deal Alert` to set `alertSent = Yes` matching on `listId + item` to prevent duplicate alerts."
},
"typeVersion": 1
},
{
"id": "275698cd-4f25-4c48-84e1-7a231a53186d",
"name": "Intent Router1",
"type": "n8n-nodes-base.switch",
"position": [
-1616,
4256
],
"parameters": {
"rules": {
"values": [
{
"outputKey": "New List",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "6517a435-aca0-4bfe-8c65-3e259a7e379c",
"operator": {
"type": "string",
"operation": "startsWith"
},
"leftValue": "={{ $json.text }}",
"rightValue": "list:"
}
]
},
"renameOutput": true
},
{
"outputKey": "Compare",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "49d5cdc0-9e0a-48fe-8635-0b5d2b42ef86",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text.toLowerCase().trim() }}",
"rightValue": "compare"
}
]
},
"renameOutput": true
},
{
"outputKey": "View Deals",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a8d35ab2-6b29-4024-80d9-77cff7e1bc91",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text.toLowerCase().trim() }}",
"rightValue": "deals"
}
]
},
"renameOutput": true
},
{
"outputKey": "My List",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "f23b8525-5f8a-49e2-a7a8-baabf56293c4",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text.toLowerCase().trim() }}",
"rightValue": "mylist"
}
]
},
"renameOutput": true
},
{
"outputKey": "Clear",
"conditions": {
"options": {
"version": 1,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "282145b9-9654-4268-bc42-a7d37bcd6805",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.text.toLowerCase().trim() }}",
"rightValue": "clear"
}
]
},
"renameOutput": true
}
]
},
"options": {
"fallbackOutput": "extra"
}
},
"typeVersion": 3
},
{
"id": "601d3afa-c9a2-439e-b735-6daf0875c582",
"name": "OpenAI \u2013 Parse List1",
"type": "n8n-nodes-base.httpRequest",
"position": [
-1184,
3696
],
"parameters": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are a shopping assistant. Extract items from the user's text into a JSON array of objects with these keys: item, qty, unit, category. Categories: Grocery, Dairy, Produce, Meat, Beverages, Household, Personal Care, Electronics, Other. Return ONLY the JSON array.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"{{ $('Wati Trigger').item.json.text }}\"\n }\n ],\n \"temperature\": 0\n} ",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
},
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "8b36382e-cb67-458f-99cf-dbda4b432b69",
"name": "Structure List Items1",
"type": "n8n-nodes-base.code",
"position": [
-960,
3696
],
"parameters": {
"jsCode": "// Structure Parsed Items \u2192 flat rows for Sheets\nconst phone = $('Wati Trigger').first().json.waId || $('Wati Trigger1').item.json.from || 'unknown';\nconst userName = $('Wati Trigger').first().json.senderName || 'Shopper';\nconst now = new Date();\nconst dateStr = now.toISOString().split('T')[0].replace(/-/g, '');\nconst rand = Math.random().toString(36).substring(2, 6).toUpperCase();\nconst listId = `LST-${dateStr}-${rand}`;\n\nconst raw = $json?.choices?.[0]?.message?.content || '[]';\nlet items = [];\ntry { items = JSON.parse(raw); } catch(e) {\n const m = raw.match(/\\[[\\s\\S]*\\]/);\n if (m) { try { items = JSON.parse(m[0]); } catch(e2) {} }\n}\n\nif (!items || items.length === 0) {\n return [{ json: {\n phone, userName, listId, itemCount: 0, rows: [], isFirst: true,\n ackMessage: `\u26a0\ufe0f Could not parse your list. Please try:\\n*list: milk 2L, eggs 12, bread, rice 5kg*`\n }}];\n}\n\nconst rows = items.map(it => ({\n listId, phone, userName,\n item: (it.item || 'item').toLowerCase(),\n qty: it.qty || 1,\n unit: it.unit || 'pcs',\n category: it.category || 'Other',\n addedAt: now.toISOString(),\n status: 'Active'\n}));\n\nconst catEmoji = { Grocery:'\ud83d\uded2\ufe0f', Dairy:'\ud83e\udd5b', Produce:'\ud83e\udd66', Meat:'\ud83e\udd69', Beverages:'\ud83e\udd64', Household:'\ud83c\udfe0', 'Personal Care':'\ud83e\uddf4', Electronics:'\ud83d\udcf1', Other:'\ud83d\udce6' };\n\nconst itemLines = items.map((it, i) =>\n ` ${i+1}. ${catEmoji[it.category]||'\ud83d\udce6'} ${it.item} \u2014 ${it.qty} ${it.unit}`\n);\n\nconst ackMessage = [\n `\u2705 *Shopping List Saved!*`,\n `\ud83d\uddc2\ufe0f List ID: \\`${listId}\\``,\n '',\n `\ud83d\udce6 *${items.length} item${items.length > 1 ? 's' : ''} added:*`,\n ...itemLines,\n '',\n '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501',\n '\ud83d\udd0d Send *compare* \u2192 AI finds best prices now',\n '\ud83d\udcb0 Send *deals* \u2192 view saved deals',\n '\ud83d\udccb Send *mylist* \u2192 view current list'\n].join('\\n');\n\nreturn rows.map((row, i) => ({ json: { ...row, ackMessage: i === 0 ? ackMessage : null, isFirst: i === 0, phone, listId, itemCount: items.length } }));"
},
"typeVersion": 2
},
{
"id": "e439642e-23db-4956-95ee-a1018058bb7a",
"name": "Google Sheets \u2013 Save Shopping List1",
"type": "n8n-nodes-base.googleSheets",
"position": [
-752,
3696
],
"parameters": {
"columns": {
"value": {
"qty": "={{ $json.qty }}",
"item": "={{ $json.item }}",
"unit": "={{ $json.unit }}",
"phone": "={{ $json.phone }}",
"Status": "={{ $json.status }}",
"listid": "={{ $json.listId }}",
"addedAt": "={{ $json.addedAt }}",
"category": "={{ $json.category }}",
"username": "={{ $json.userName }}"
},
"schema": [
{
"id": "listid",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "listid",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "username",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "username",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "item",
"type": "string",
"display": true,
"required": false,
"displayName": "item",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "qty",
"type": "string",
"display": true,
"required": false,
"displayName": "qty",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "unit",
"type": "string",
"display": true,
"required": false,
"displayName": "unit",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "category",
"type": "string",
"display": true,
"required": false,
"displayName": "category",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "addedAt",
"type": "string",
"display": true,
"required": false,
"displayName": "addedAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Status",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "Status",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit#gid=0",
"cachedResultName": "Shopping List"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "6f312e5e-4cc3-4e8a-85c7-f442635645a2",
"name": "Google Sheets \u2013 Read My List1",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1168,
4704
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit#gid=0",
"cachedResultName": "Shopping List"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "0bad7b13-b2e6-4c18-8e76-d719c78b1ee9",
"name": "Build My List View1",
"type": "n8n-nodes-base.code",
"position": [
-912,
4704
],
"parameters": {
"jsCode": "const phone = $('Wati Trigger').first().json.waId || $('Wati Trigger1').item.json.from;\nconst userName = $('Wati Trigger').first().json.senderName|| 'Shopper';\nconst items = $input.all().filter(r => r.json.phone === phone && (r.json.status||'').toLowerCase() === 'active');\n\nif (items.length === 0) {\n return [{ json: { phone, msg: `\ud83d\udccb *Your list is empty, ${userName}!*\\n\\nAdd items with:\\n*list: milk 2L, eggs 12, bread, rice 5kg*` } }];\n}\n\nconst catEmoji = { Grocery:'\ud83d\uded2\ufe0f', Dairy:'\ud83e\udd5b', Produce:'\ud83e\udd66', Meat:'\ud83e\udd69', Beverages:'\ud83e\udd64', Household:'\ud83c\udfe0', 'Personal Care':'\ud83e\uddba', Electronics:'\ud83d\udcf1', Other:'\ud83d\udce6' };\nconst byCategory = {};\nfor (const r of items) {\n const c = r.json.category || 'Other';\n if (!byCategory[c]) byCategory[c] = [];\n byCategory[c].push(r.json);\n}\n\nconst lines = [`\ud83d\udccb *Your Shopping List*`, `\ud83d\udc64 ${userName}`, ''];\nfor (const [cat, rows] of Object.entries(byCategory)) {\n lines.push(`${catEmoji[cat]||'\ud83d\udce6'} *${cat}*`);\n for (const it of rows) lines.push(` \u2022 ${it.item} \u2014 ${it.qty} ${it.unit}`);\n lines.push('');\n}\nlines.push(`\ud83d\udce6 *Total: ${items.length} items*`);\nlines.push('\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501');\nlines.push('Send *compare* to find best prices \ud83d\udd0d');\n\nreturn [{ json: { phone, msg: lines.join('\\n') } }];"
},
"typeVersion": 2
},
{
"id": "9c6454b9-afda-4888-a666-130b2d9fbc3a",
"name": "Google Sheets \u2013 Read Saved Deals1",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1184,
5104
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1354101119,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit#gid=1354101119",
"cachedResultName": "Deals"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "b1a21a36-3c98-467a-b991-c96146670369",
"name": "Build Deals View1",
"type": "n8n-nodes-base.code",
"position": [
-864,
5104
],
"parameters": {
"jsCode": "const phone = $('Wati Trigger').first().json.waId || $('Wati Trigger1').item.json.from;\nconst userName = $('Wati Trigger').first().json.senderName || 'Shopper';\nconst allRows = $input.all();\n\nconst myDeals = allRows.filter(r => r.json.phone === phone).sort((a,b)=>new Date(b.json.foundAt)-new Date(a.json.foundAt));\nconst seen = new Set();\nconst latest = myDeals.filter(r => { if (seen.has(r.json.item)) return false; seen.add(r.json.item); return true; });\n\nif (latest.length === 0) {\n return [{ json: { phone, msg: `\ud83d\udcb0 *No saved deals yet, ${userName}!*\\n\\nSend *compare* to find the best prices for your list.` } }];\n}\n\nconst lines = [`\ud83d\udcb0 *Your Saved Deals*`, `\ud83d\udc64 ${userName}`, ''];\nfor (const r of latest) {\n const saving = parseFloat($input.first().json.savingPct) > 0 ? ` \ud83c\udff7\ufe0f Save ${r.json.savingPct}%` : '';\n const dt = r.json.foundAt ? new Date(r.json.foundAt).toLocaleDateString('en-IN',{day:'numeric',month:'short'}) : '';\n lines.push(`\ud83d\uded2 *${r.json.item}*`);\n lines.push(` \u2705 ${r.json.storeName} \u2014 \u20b9${r.json.price}${saving}`);\n if (r.json.url) lines.push(` \ud83d\udd17 ${r.json.url}`);\n lines.push(` \ud83d\udcc5 Found: ${dt}`);\n lines.push('');\n}\nlines.push('\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501');\nlines.push('Send *compare* to refresh prices \ud83d\udd04');\nreturn [{ json: { phone, msg: lines.join('\\n') } }];"
},
"typeVersion": 2
},
{
"id": "0da98dbc-8018-4c14-be6d-3091268a61be",
"name": "Process Clear1",
"type": "n8n-nodes-base.code",
"position": [
-1136,
5504
],
"parameters": {
"jsCode": "const phone = $json.waId || $json.from;\nconst name = $json.senderName || 'Shopper';\nreturn [{ json: { phone, msg: `\ud83d\uddd1\ufe0f *List cleared, ${name}!*\\n\\nStart fresh anytime:\\n*list: item1, item2, item3*` } }];"
},
"typeVersion": 2
},
{
"id": "e7d9692b-f8bb-4eac-98c6-5edc140c3961",
"name": "Google Sheets \u2013 Read List for Compare1",
"type": "n8n-nodes-base.googleSheets",
"position": [
-1200,
4352
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit#gid=0",
"cachedResultName": "Shopping List"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "9508c89f-bc70-43b3-b576-7832139fb32a",
"name": "Prepare Search Queries1",
"type": "n8n-nodes-base.code",
"position": [
-928,
4352
],
"parameters": {
"jsCode": "// Prepare one search query per item for this user\nconst phone = $('Intent Router1').first().json.waId|| $('Wati Trigger1').item.json.from;\nconst rows = $input.all().filter(r => r.json.phone === phone && (r.json.status||'').toLowerCase() === 'active');\n\nif (rows.length === 0) {\n return [{ json: { phone, noItems: true, searchQuery: null,\n errMsg: `\ud83d\udccb *Your list is empty!*\\n\\nAdd items first with: *list: milk 2L, eggs*`\n }}];\n}\n\nreturn rows.map(r => ({\n json: {\n phone,\n userName: r.json.userName,\n listId: r.json.listId,\n item: r.json.item,\n qty: r.json.qty,\n unit: r.json.unit,\n category: r.json.category,\n searchQuery: `best price ${r.json.item} ${r.json.qty}${r.json.unit} buy online India`,\n noItems: false\n }\n}));"
},
"typeVersion": 2
},
{
"id": "9648270c-dc21-4ed1-9478-2d917ed38297",
"name": "Serper \u2013 Search Prices1",
"type": "n8n-nodes-base.httpRequest",
"position": [
-688,
4352
],
"parameters": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"You are a price comparison expert. Analyze the Google Shopping search results provided. 1. Identify the 'bestDeal' (lowest price from a reputable store). 2. Find up to 2 'alternatives'. 3. Calculate 'savingPct' if an original price is visible. 4. Return ONLY a JSON object with these keys: bestDeal (storeName, price, originalPrice, savingPct, url, currency), alternatives (array of storeName and price), confidence (high/low), and summary.\"\n },\n {\n \"role\": \"user\",\n \"content\": \"Search results for {{ $('Prepare Search Queries1').item.json.item }}: {{ JSON.stringify($json.shopping) }}\"\n }\n ],\n \"response_format\": { \"type\": \"json_object\" },\n \"temperature\": 0\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
},
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "169321ed-6956-4fb7-8519-038878488530",
"name": "OpenAI \u2013 Extract Best Deal1",
"type": "n8n-nodes-base.httpRequest",
"position": [
-464,
4352
],
"parameters": {
"url": "https://api.openai.com/v1/chat/completions",
"method": "POST",
"options": {},
"jsonBody": "={\n \"model\": \"gpt-4o-mini\",\n \"messages\": [\n {\n \"role\": \"system\",\n \"content\": \"Analyze the shopping search results. Identify the best deal (lowest price from a reputable store). Return a JSON object with: 'bestDeal' (object with storeName, price, originalPrice, savingPct, url, currency), 'alternatives' (array of up to 2 other stores/prices), 'confidence' (high/low), and 'summary' (brief explanation).\"\n },\n {\n \"role\": \"user\",\n \"content\": \"={{ JSON.stringify($json.shopping) }}\"\n }\n ],\n \"response_format\": { \"type\": \"json_object\" },\n \"temperature\": 0\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"authentication": "predefinedCredentialType",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
}
]
},
"nodeCredentialType": "openAiApi"
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
},
"httpHeaderAuth": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "af99be87-074e-45e2-a491-b0bb268e0478",
"name": "Process Deal Result1",
"type": "n8n-nodes-base.code",
"position": [
-240,
4352
],
"parameters": {
"jsCode": "// Parse deal, build item card, prepare Sheets row\nconst raw = $json?.choices?.[0]?.message?.content || '{}';\nlet d = {};\ntry { d = JSON.parse(raw); } catch(e) { const m = raw.match(/\\{[\\s\\S]*\\}/); if(m){try{d=JSON.parse(m[0]);}catch(e2){}} }\n\nconst src = $('Prepare Search Queries1').item.json;\nconst best = d.bestDeal || {};\nconst alts = d.alternatives || [];\nconst now = new Date();\n\nconst saveBadge = (best.savingPct||0) > 0 ? ` \ud83c\udff7\ufe0f -${best.savingPct}%` : '';\nconst altLines = alts.slice(0,2).map(a => ` \u2022 ${a.storeName}: \u20b9${a.price}`).join('\\n');\n\nconst itemCard = [\n `\\n\ud83d\uded2 *${src.item}* (${src.qty} ${src.unit})`,\n ` \u2705 *${best.storeName||'Unknown'}* \u2014 \u20b9${best.price||'N/A'}${saveBadge}`,\n altLines ? ` Others:\\n${altLines}` : '',\n best.url ? ` \ud83d\udd17 ${best.url}` : ''\n].filter(Boolean).join('\\n');\n\nreturn [{ json: {\n listId: src.listId,\n phone: src.phone,\n userName: src.userName,\n item: src.item,\n qty: src.qty,\n unit: src.unit,\n storeName: best.storeName || 'Unknown',\n price: best.price || 0,\n originalPrice: best.originalPrice || best.price || 0,\n savingPct: best.savingPct || 0,\n currency: best.currency || 'INR',\n url: best.url || '',\n summary: d.summary || '',\n confidence: d.confidence || 'low',\n alternatives: JSON.stringify(alts),\n foundAt: now.toISOString(),\n alertSent: 'No',\n itemCard\n}}];"
},
"typeVersion": 2
},
{
"id": "c2e8e927-2812-45e5-a71d-40b934f38b84",
"name": "Google Sheets \u2013 Save Deals1",
"type": "n8n-nodes-base.googleSheets",
"position": [
16,
4352
],
"parameters": {
"columns": {
"value": {
"url": "={{ $json.url }}",
"item": "={{ $node['Google Sheets \u2013 Read List for Compare1'].json.item }}",
"phone": "={{ $node['Google Sheets \u2013 Read List for Compare1'].json.phone }}",
"price": "={{ $json.price }}",
"listid": "={{ $node[\"Google Sheets \u2013 Read List for Compare1\"].json.listid }}",
"foundAt": "={{ $json.foundAt }}",
"summary": "={{ $json.summary }}",
"Currency": "={{ $json.currency }}",
"alertSent": "={{ $json.alertSent }}",
"savingPct": "={{ $json.savingPct }}",
"storeName": "={{ $json.storeName }}",
"confidence": "={{ $json.confidence }}",
"alternatives": "={{ $json.alternatives }}",
"originalPrice": "={{ $json.originalPrice }}"
},
"schema": [
{
"id": "listid",
"type": "string",
"display": true,
"required": false,
"displayName": "listid",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "phone",
"type": "string",
"display": true,
"required": false,
"displayName": "phone",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "item",
"type": "string",
"display": true,
"required": false,
"displayName": "item",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "storeName",
"type": "string",
"display": true,
"required": false,
"displayName": "storeName",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "price",
"type": "string",
"display": true,
"required": false,
"displayName": "price",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "originalPrice",
"type": "string",
"display": true,
"required": false,
"displayName": "originalPrice",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "savingPct",
"type": "string",
"display": true,
"required": false,
"displayName": "savingPct",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "Currency",
"type": "string",
"display": true,
"required": false,
"displayName": "Currency",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "url",
"type": "string",
"display": true,
"required": false,
"displayName": "url",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "summary",
"type": "string",
"display": true,
"required": false,
"displayName": "summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "confidence",
"type": "string",
"display": true,
"required": false,
"displayName": "confidence",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "alternatives",
"type": "string",
"display": true,
"required": false,
"displayName": "alternatives",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "foundAt",
"type": "string",
"display": true,
"required": false,
"displayName": "foundAt",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "alertSent",
"type": "string",
"display": true,
"required": false,
"displayName": "alertSent",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1354101119,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit#gid=1354101119",
"cachedResultName": "Deals"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nLiq8cy7O-qfdDLtpzzAmDNDhIpR4-HCLS6c-y8v5-0/edit?usp=drivesdk",
"cachedResultName": "Untitled spreadsheet"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "580a8a1e-2d20-4b59-8d66-26fa883c3251",
"name": "Aggregate Comparison Card1",
"type": "n8n-nodes-base.code",
"position": [
256,
4352
],
"parameters": {
"jsCode": "// Aggregate all processed deal items into one comparison card\nconst all = $input.all();\nif (!all || all.length === 0) return [{ json: { phone:'', card:'No deals found.' } }];\n\nconst phone = all[0].json.phone;\nconst userName = all[0].json.userName;\nconst now = new Date();\n\nlet totalBest = 0, totalOrig = 0;\nconst cards = all.map(r => { totalBest += parseFloat(r.json.price||0); totalOrig += parseFloat(r.json.originalPrice||r.json.price||0); return r.json.itemCard; }).join('\\n');\n\nconst saved = totalOrig - totalBest;\nconst savePct = totalOrig > 0 ? Math.round((saved / totalOrig) * 100) : 0;\nconst hiConf = all.filter(r => r.json.confidence === 'high').length;\n\nconst lines = [\n `\ud83d\udd0d *Price Comparison Report*`,\n `\ud83d\udc64 ${userName} \u00b7 ${now.toLocaleDateString('en-IN',{day:'numeric',month:'short'})}`,\n cards,\n '\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501',\n `\ud83d\udcb0 *Estimated Total: \u20b9${totalBest.toFixed(0)}*`,\n saved > 0 ? `\ud83c\udff7\ufe0f *You could save: \u20b9${saved.toFixed(0)} (${savePct}%)*` : '',\n `\u2139\ufe0f ${hiConf}/${all.length} prices high-confidence`,\n '',\n 'Prices are live estimates \u2014 verify before purchase.',\n 'Send *deals* anytime to review saved deals.'\n].filter(l => l !== '').join('\\n');\n\nreturn [{ json: { phone, card: lines } }];"
},
"typeVersion": 2
},
{
"id": "fa355182-765f-4690-8290-fcce9a7f4750",
"name": "Schedule Trigger \u2013 Daily 8AM Alerts1",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
1280,
4672
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * *"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "0b0341cf-7ad4-4cbf-88fa-ecc9095bddde",
"name": "Google Sheets \u2013 Read Deals for Alert1",
"type": "n8n-nodes-base.googleSheets",
"position": [
1520,
4672
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "Deals"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_GOOGLE_SHEET_ID"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.5
},
{
"id": "10b9c976-1234-4533-aad3-e7f959280019",
"name": "Build Alert Messages1",
"type": "n8n-nodes-base.code",
"position": [
1760,
4672
],
"parameters": {
"jsCode": "// Build personalised daily deal alerts\nconst allRows = $input.all();\n\nconst alertable = allRows.filter(r => r.json.alertSent === 'No' && parseFloat(r.json.savingPct||0) >= 10);\nif (alertable.length === 0) return [{ json: { message: 'No deals to alert today', count: 0 } }];\n\nconst byPhone = {};\nfor (const r of alertable) {\n const p = r.json.phone;\n if (!byPhone[p]) byPhone[p] = { phone: p, userName: r.json.userName, deals: [] };\n byPhone[p].deals.push(r.json);\n}\n\nreturn Object.values(byPhone).map(user => {\n const top = user.deals.sort((a,b)=>parseFloat(b.savingPct)-parseFloat(a.savingPct)).slice(0,5);\n const lines = [\n `\ud83d\udd25 *Daily Deal Alert!*`,\n `\ud83d\udc4b Hi *${user.userName}!* Best prices found for your list:`,\n ''\n ];\n for (const d of top) {\n lines.push(`\ud83d\uded2 *${d.item}* \u2014 \u20b9${d.price} at *${d.storeName}*`);\n lines.push(` \ud83c\udff7\ufe0f Save ${d.savingPct}% vs original price`);\n if (d.url) lines.push(` \ud83d\udd17 ${d.url}`);\n lines.push('');\n }\n lines.push('\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501');\n lines.push('Send *compare* to refresh prices anytime \ud83d\udd04');\n return { json: { phone: user.phone, alertMessage: lines.join('\\n'), dealCount: top.length } };\n});"
},
"typeVersion": 2
},
{
"id": "9698ad3b-7ab1-4675-8665-b6cc8fb755cf",
"name": "Send a text message5",
"type": "n8n-nodes-wati.wati",
"onError": "continueRegularOutput",
"position": [
-528,
3696
],
"parameters": {
"target": "={{ $('Wati Trigger').item.json.waId }}",
"messageText": "={{ $('Structure List Items1').item.json.ackMessage }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "8c745c6c-06f2-41ac-8362-bd7b444947e2",
"name": "Send a text message6",
"type": "n8n-nodes-wati.wati",
"position": [
464,
4352
],
"parameters": {
"target": "={{ $node['Wati Trigger'].json.waId }}",
"messageText": "={{ $json.card }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "b55e03ed-b5bb-4aa2-88b9-3305323a86e3",
"name": "Send a text message7",
"type": "n8n-nodes-wati.wati",
"position": [
-656,
4704
],
"parameters": {
"target": "={{ $node['Wati Trigger'].json.waId }}",
"messageText": "={{ $json.msg }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "bba952b7-599a-478c-8f1e-b47f3f6f0541",
"name": "Send a text message8",
"type": "n8n-nodes-wati.wati",
"position": [
-576,
5104
],
"parameters": {
"target": "={{ $('Wati Trigger').item.json.waId }}",
"messageText": "={{ $json.msg }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "e5422e68-f0a6-4a59-affb-b50814add91c",
"name": "Send a text message9",
"type": "n8n-nodes-wati.wati",
"position": [
-832,
5504
],
"parameters": {
"target": "={{ $json.phone }}",
"messageText": "={{ $json.msg }}"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "5eb4b0e0-5bf2-46e7-b64e-8d764b6b3dd2",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1248,
3552
],
"parameters": {
"color": 7,
"width": 1136,
"height": 384,
"content": "**New List path:**\n`OpenAI \u2013 Parse List` \u2192 `Structure List Items` (generates one row per item + ackMessage) \u2192 `Google Sheets \u2013 Save Shopping List` \u2192 `Send a text message` (confirm to user)"
},
"typeVersion": 1
},
{
"id": "0216abdb-cd4d-4b44-8b3c-e9b6047c0296",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1264,
5376
],
"parameters": {
"color": 7,
"width": 1040,
"height": 272,
"content": "\nSticky Note Title: \ud83e\uddf9 MAINTENANCE\nFunction: Handles the \"Clear\" intent.\nNote: This node currently prepares the \"Success\" message.\nTip: Ensure you have a Google Sheets node following this (or a specific script) if you want to physically delete rows or update status to 'inactive' in your database."
},
"typeVersion": 1
},
{
"id": "bb3d20d1-6036-47d5-9fcb-031850a92415",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1280,
4992
],
"parameters": {
"color": 7,
"width": 1056,
"height": 320,
"content": "Sticky Note Title: \ud83d\udcb0 SAVINGS TRACKER\nFunction: Fetches the latest price data from the Deals sheet. Key Features:* Sorts by date to show the most recent prices first.De-duplicates items so the user only sees the latest deal per product.Calculates and displays Saving % and store links."
},
"typeVersion": 1
},
{
"id": "7368a5fa-05c3-4b23-b627-8b68aa4468cd",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1264,
4576
],
"parameters": {
"color": 7,
"width": 928,
"height": 320,
"content": "Nodes: Google Sheets \u2013 Read My List1 & Build My List View1\n\nFunction: Pulls active shopping items from the ShoppingList sheet.Groups items by Category (Dairy, Produce, etc.).Adds specific emojis based on the category for a better mobile UI.Output: Formatted WhatsApp message with a summary and total count."
},
"typeVersion": 1
},
{
"id": "00eea83e-1f55-40cd-8202-90793c3efa32",
"name": "Wati Trigger",
"type": "n8n-nodes-wati.watiTrigger",
"position": [
-1824,
4320
],
"parameters": {
"event": "messageReceived"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1,
"alwaysOutputData": true
},
{
"id": "38f7f040-5864-4d76-b050-bb5325bdca4e",
"name": "Send a text message",
"type": "n8n-nodes-wati.wati",
"position": [
2048,
4672
],
"parameters": {
"target": "78523",
"messageText": "t"
},
"credentials": {
"watiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
}
],
"connections": {
"Wati Trigger": {
"main": [
[
{
"node": "Intent Router1",
"type": "main",
"index": 0
}
]
]
},
"Intent Router1": {
"main": [
[
{
"node": "OpenAI \u2013 Parse List1",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Sheets \u2013 Read List for Compare1",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Sheets \u2013 Read Saved Deals1",
"type": "main",
"index": 0
}
],
[
{
"node": "Google Sheets \u2013 Read My List1",
"type": "main",
"index": 0
}
],
[
{
"node": "Process Clear1",
"type": "main",
"index": 0
}
]
]
},
"Process Clear1": {
"main": [
[
{
"node": "Send a text message9",
"type": "main",
"index": 0
}
]
]
},
"Build Deals View1": {
"main": [
[
{
"node": "Send a text message8",
"type": "main",
"index": 0
}
]
]
},
"Build My List View1": {
"main": [
[
{
"node": "Send a text message7",
"type": "main",
"index": 0
}
]
]
},
"Process Deal Result1": {
"main": [
[
{
"node": "Google Sheets \u2013 Save Deals1",
"type": "main",
"index": 0
}
]
]
},
"Build Alert Messages1": {
"main": [
[
{
"node": "Send a text message",
"type": "main",
"index": 0
}
]
]
},
"Structure List Items1": {
"main": [
[
{
"node": "Google Sheets \u2013 Save Shopping List1",
"type": "main",
"index": 0
}
]
]
},
"OpenAI \u2013 Parse List1": {
"main": [
[
{
"node": "Structure List Items1",
"type": "main",
"index": 0
}
]
]
},
"Prepare Search Queries1": {
"main": [
[
{
"node": "Serper \u2013 Search Prices1",
"type": "main",
"index": 0
}
]
]
},
"Serper \u2013 Search Prices1": {
"main": [
[
{
"node": "OpenAI \u2013 Extract Best Deal1",
"type": "main",
"index": 0
}
]
]
},
"Aggregate Comparison Card1": {
"main": [
[
{
"node": "Send a text message6",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Save Deals1": {
"main": [
[
{
"node": "Aggregate Comparison Card1",
"type": "main",
"index": 0
}
]
]
},
"OpenAI \u2013 Extract Best Deal1": {
"main": [
[
{
"node": "Process Deal Result1",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Read My List1": {
"main": [
[
{
"node": "Build My List View1",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Read Saved Deals1": {
"main": [
[
{
"node": "Build Deals View1",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Save Shopping List1": {
"main": [
[
{
"node": "Send a text message5",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger \u2013 Daily 8AM Alerts1": {
"main": [
[
{
"node": "Google Sheets \u2013 Read Deals for Alert1",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Read Deals for Alert1": {
"main": [
[
{
"node": "Build Alert Messages1",
"type": "main",
"index": 0
}
]
]
},
"Google Sheets \u2013 Read List for Compare1": {
"main": [
[
{
"node": "Prepare Search Queries1",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
googleSheetsOAuth2ApihttpHeaderAuthopenAiApiwatiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Elevate your shopping experience with an AI-driven personal assistant that lives right in your WhatsApp. This template automates the entire lifecycle of a shopping list—from intelligent intake and live web price comparisons to proactive daily deal alerts—by combining WATI,…
Source: https://n8n.io/workflows/13722/ — 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.
Auto Hunt. Uses httpRequest, googleSheets, rssFeedRead, telegram. Scheduled trigger; 78 nodes.
Streamline your automotive service center's operations with this comprehensive automation. This workflow manages the entire customer lifecycle—from automated service reminders and instant appointment
. Uses googleSheets, telegram, httpRequest, wise. Scheduled trigger; 36 nodes.
Streamline your clinic's operations with a fully automated patient communication system. This workflow manages the entire appointment lifecycle—from automated morning reminders to real-time confirmati
This workflow automates plant care reminders and records using Google Sheets, Telegram, and OpenWeather API.