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.
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
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.
Apollo Data Enrichment Using Company Id to automatically finds contacts for companies listed in your Google Sheet, enriches each person with emails and phone numbers via Apollo’s API, and writes verif
This workflow contains community nodes that are only compatible with the self-hosted version of n8n.