This workflow corresponds to n8n.io template #16288 — we link there as the canonical source.
This workflow follows the Agent → 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": "1riq5uToZ9t7XdxE",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Messaging Delta Tracker",
"tags": [],
"nodes": [
{
"id": "3f5282c6-b5a6-48e9-bd91-4457a77a5e1f",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-1120,
128
],
"parameters": {
"rule": {
"interval": [
{}
]
}
},
"typeVersion": 1.3
},
{
"id": "ab6ec9d5-d4f5-4399-88a6-7622f4793227",
"name": "Read Competitors",
"type": "n8n-nodes-base.googleSheets",
"position": [
-896,
128
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "Pending",
"lookupColumn": "status"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit#gid=0",
"cachedResultName": "competitors"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit?usp=drivesdk",
"cachedResultName": "4th template"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "d77882e4-4de0-4853-bf99-939d41581bb1",
"name": "Loop Over Competitors",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-672,
128
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "5196364e-5f65-469e-959c-b142ba7e59e1",
"name": "Search Facebook Ads",
"type": "n8n-nodes-adyntel.adyntel",
"position": [
-368,
-64
],
"parameters": {
"companyDomain": "={{ $json.domain }}",
"requestOptions": {}
},
"credentials": {
"adyntelApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "dc714a23-8ade-439b-b6c7-f3be676df82d",
"name": "Search Google Ads",
"type": "n8n-nodes-adyntel.adyntel",
"position": [
-368,
128
],
"parameters": {
"resource": "googleAds",
"companyDomain": "={{ $json.domain }}",
"requestOptions": {}
},
"credentials": {
"adyntelApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "e965dc84-0720-4cad-997e-9fb571f7833e",
"name": "Search LinkedIn Ads",
"type": "n8n-nodes-adyntel.adyntel",
"position": [
-368,
336
],
"parameters": {
"resource": "linkedInAds",
"companyDomain": "={{ $json.domain }}",
"requestOptions": {}
},
"credentials": {
"adyntelApi": {
"name": "<your credential>"
}
},
"typeVersion": 1
},
{
"id": "b219b08c-e1cc-425f-9bf1-7d5ce83a87d2",
"name": "Merge All Platform Results",
"type": "n8n-nodes-base.merge",
"position": [
-144,
112
],
"parameters": {
"mode": "combine",
"options": {},
"combineBy": "combineByPosition",
"numberInputs": 3
},
"typeVersion": 3.2
},
{
"id": "5cf489a8-fd88-41a7-b5da-e99a761230bf",
"name": "Extract This Week's Copy",
"type": "n8n-nodes-base.code",
"position": [
80,
128
],
"parameters": {
"jsCode": "// ===== EXTRACT AD COPY FROM ALL PLATFORMS =====\n\nfunction extractTitle(ad) {\n if (ad.headline && typeof ad.headline === 'object') {\n const t = ad.headline.title || ad.headline.description;\n if (t && !t.includes('{{')) return t;\n }\n if (ad.commentary && ad.commentary.text && !ad.commentary.text.includes('{{')) {\n return ad.commentary.text.substring(0, 120);\n }\n if (ad.snapshot) {\n if (Array.isArray(ad.snapshot.cards) && ad.snapshot.cards.length > 0) {\n for (const card of ad.snapshot.cards) {\n if (card.title && !card.title.includes('{{')) return card.title;\n if (card.body && typeof card.body === 'string' && !card.body.includes('{{')) return card.body.substring(0, 120);\n }\n }\n if (ad.snapshot.title && !ad.snapshot.title.includes('{{')) return ad.snapshot.title;\n if (ad.snapshot.body) {\n const bodyText = typeof ad.snapshot.body === 'object' ? ad.snapshot.body.text : ad.snapshot.body;\n if (bodyText && !bodyText.includes('{{')) return bodyText.substring(0, 120);\n }\n }\n if (typeof ad.title === 'string' && !ad.title.includes('{{')) return ad.title;\n if (typeof ad.ad_title === 'string' && ad.ad_title !== 'N/A') return ad.ad_title;\n return null;\n}\n\nfunction extractBody(ad) {\n // Try to get longer body copy beyond the title\n if (ad.snapshot) {\n if (ad.snapshot.body) {\n const b = typeof ad.snapshot.body === 'object' ? ad.snapshot.body.text : ad.snapshot.body;\n if (b && b.length > 0) return b.substring(0, 300);\n }\n if (Array.isArray(ad.snapshot.cards) && ad.snapshot.cards.length > 0) {\n const bodies = ad.snapshot.cards\n .map(c => c.body || c.description || '')\n .filter(Boolean)\n .join(' | ');\n if (bodies) return bodies.substring(0, 300);\n }\n }\n if (ad.commentary && ad.commentary.text) return ad.commentary.text.substring(0, 300);\n if (ad.description) return String(ad.description).substring(0, 300);\n return '';\n}\n\nfunction getMonday(d) {\n const date = new Date(d);\n const day = date.getDay();\n const diff = date.getDate() - day + (day === 0 ? -6 : 1);\n date.setDate(diff);\n return date.toISOString().split('T')[0];\n}\n\nconst items = $input.all();\nconst adRows = [];\nconst weekOf = getMonday(new Date());\n\n// Get domain and competitor_name from Loop node\nlet domain = '';\nlet competitorName = '';\ntry {\n const loopItem = $('Loop Over Competitors').item.json;\n domain = loopItem.domain || loopItem.Domain || '';\n competitorName = loopItem.competitor_name || loopItem.Competitor_Name || loopItem.name || loopItem.Name || '';\n} catch(e) {\n for (const item of items) {\n if (item.json.domain) { domain = item.json.domain; }\n if (item.json.competitor_name) { competitorName = item.json.competitor_name; }\n }\n}\n\nconst snapshotId = `${domain}_${weekOf}`;\n\nfor (const item of items) {\n const data = item.json;\n\n // Facebook/Meta\n if (data.results && Array.isArray(data.results)) {\n const fbAds = data.results.flat();\n for (const ad of fbAds) {\n const title = extractTitle(ad);\n if (!title) continue;\n adRows.push({\n snapshot_id: snapshotId,\n domain: domain,\n competitor_name: competitorName,\n week_of: weekOf,\n platform: 'Meta',\n ad_title: title,\n ad_body: extractBody(ad),\n captured_at: new Date().toISOString()\n });\n }\n }\n\n // LinkedIn / Google\n if (data.ads && Array.isArray(data.ads)) {\n for (const ad of data.ads) {\n let platform = 'Google';\n if (ad.view_details_link && ad.view_details_link.includes('linkedin.com')) {\n platform = 'LinkedIn';\n } else if (ad.headline || ad.commentary) {\n platform = 'LinkedIn';\n }\n const title = extractTitle(ad);\n if (!title) continue;\n adRows.push({\n snapshot_id: snapshotId,\n domain: domain,\n competitor_name: competitorName,\n week_of: weekOf,\n platform: platform,\n ad_title: title,\n ad_body: extractBody(ad),\n captured_at: new Date().toISOString()\n });\n }\n }\n}\n\n// Deduplicate by ad_title + platform\nconst seen = new Set();\nconst deduped = adRows.filter(row => {\n const key = `${row.platform}::${row.ad_title}`;\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n});\n\nif (deduped.length === 0) {\n // No ads found \u2014 return a sentinel so downstream nodes can handle gracefully\n return [{\n json: {\n snapshot_id: snapshotId,\n domain: domain,\n competitor_name: competitorName,\n week_of: weekOf,\n platform: 'N/A',\n ad_title: 'NO_ADS_FOUND',\n ad_body: '',\n captured_at: new Date().toISOString(),\n _no_ads: true\n }\n }];\n}\n\nreturn deduped.map(r => ({ json: r }));"
},
"typeVersion": 2
},
{
"id": "05eae710-ec1f-469a-bcc7-ea3915188e09",
"name": "Aggregate This Week's Copy",
"type": "n8n-nodes-base.code",
"position": [
304,
128
],
"parameters": {
"jsCode": "// ===== AGGREGATE THIS WEEK'S COPY INTO A SINGLE CONTEXT OBJECT =====\n// Groups all ad rows for this competitor into one item for comparison\n\nconst items = $input.all();\nif (!items || items.length === 0) {\n return [{ json: { error: 'No input items', _skip: true } }];\n}\n\nconst first = items[0].json;\n\n// If no ads found sentinel\nif (first._no_ads) {\n return [{\n json: {\n domain: first.domain,\n competitor_name: first.competitor_name,\n week_of: first.week_of,\n snapshot_id: first.snapshot_id,\n this_week_rows: [],\n this_week_text: 'No ads found this week.',\n _no_ads: true\n }\n }];\n}\n\nconst domain = first.domain;\nconst competitorName = first.competitor_name;\nconst weekOf = first.week_of;\nconst snapshotId = first.snapshot_id;\n\n// Build a readable text block of all this week's ads for the LLM\nconst allRows = items.map(i => i.json);\nconst lines = allRows.map(r => {\n const body = r.ad_body ? ` | Body: ${r.ad_body}` : '';\n return `[${r.platform}] ${r.ad_title}${body}`;\n});\n\nconst thisWeekText = lines.join('\\n');\n\nreturn [{\n json: {\n domain: domain,\n competitor_name: competitorName,\n week_of: weekOf,\n snapshot_id: snapshotId,\n this_week_rows: allRows,\n this_week_text: thisWeekText,\n _no_ads: false\n }\n}];"
},
"typeVersion": 2
},
{
"id": "0e1d5854-43f9-4c72-b409-2a653fcbbed2",
"name": "Read Last Week Snapshots",
"type": "n8n-nodes-base.googleSheets",
"position": [
528,
128
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $json.domain }}",
"lookupColumn": "domain"
},
{
"lookupValue": "={{ $now.minus({days: 7}).startOf('week').toISODate() }}",
"lookupColumn": "week_of"
}
]
},
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1910720088,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit#gid=1910720088",
"cachedResultName": "ad_copy_snapshots"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit?usp=drivesdk",
"cachedResultName": "4th template"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "d38c9340-6ae7-4852-b31f-74776f6286c4",
"name": "Build Comparison Context",
"type": "n8n-nodes-base.code",
"position": [
752,
128
],
"parameters": {
"jsCode": "// ===== FIND LAST WEEK'S SNAPSHOT & BUILD COMPARISON CONTEXT =====\n\nconst allItems = $input.all();\n\n// Get the aggregated this-week data from upstream\nlet thisWeekData = {};\ntry {\n thisWeekData = $('Aggregate This Week\\'s Copy').first().json;\n} catch(e) {\n return [{ json: { error: 'Could not read this week data', _skip: true } }];\n}\n\nconst domain = thisWeekData.domain;\nconst weekOf = thisWeekData.week_of;\nconst thisWeekText = thisWeekData.this_week_text || 'No ads found this week.';\n\n// Calculate last Monday's date\nfunction getPreviousMonday(weekOfStr) {\n const d = new Date(weekOfStr);\n d.setDate(d.getDate() - 7);\n return d.toISOString().split('T')[0];\n}\nconst previousWeek = getPreviousMonday(weekOf);\n\n// Filter sheet rows to only last week's entries for this domain\nconst lastWeekRows = allItems\n .map(i => i.json)\n .filter(r => r.domain === domain && r.week_of === previousWeek);\n\nlet lastWeekText = '';\nif (lastWeekRows.length === 0) {\n lastWeekText = 'No previous week data available (first run or no ads last week).';\n} else {\n const lines = lastWeekRows.map(r => {\n const body = r.ad_body ? ` | Body: ${r.ad_body}` : '';\n return `[${r.platform}] ${r.ad_title}${body}`;\n });\n lastWeekText = lines.join('\\n');\n}\n\n// Build unique title sets for new/dropped detection\nconst thisWeekTitles = new Set(\n (thisWeekData.this_week_rows || []).map(r => r.ad_title)\n);\nconst lastWeekTitles = new Set(\n lastWeekRows.map(r => r.ad_title)\n);\n\nconst newTitles = [...thisWeekTitles].filter(t => !lastWeekTitles.has(t));\nconst droppedTitles = [...lastWeekTitles].filter(t => !thisWeekTitles.has(t));\n\nreturn [{\n json: {\n domain: domain,\n competitor_name: thisWeekData.competitor_name,\n week_of: weekOf,\n previous_week: previousWeek,\n snapshot_id: thisWeekData.snapshot_id,\n this_week_text: thisWeekText,\n last_week_text: lastWeekText,\n new_titles: newTitles,\n dropped_titles: droppedTitles,\n this_week_rows: thisWeekData.this_week_rows || [],\n is_first_run: lastWeekRows.length === 0,\n _no_ads: thisWeekData._no_ads || false\n }\n}];"
},
"typeVersion": 2
},
{
"id": "df33159a-195a-40e5-8910-a1e6d619884d",
"name": "Has Ads This Week?",
"type": "n8n-nodes-base.if",
"position": [
1008,
128
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d024acc6-3584-4bf9-93ff-5f672c393b74",
"operator": {
"type": "boolean",
"operation": "equals"
},
"leftValue": "={{ $json._no_ads }}",
"rightValue": true
}
]
}
},
"typeVersion": 2.3
},
{
"id": "8747426b-2d8d-45f4-8f2d-ef61d5a90c1e",
"name": "AI Agent \u2014 Detect Messaging Delta",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
1232,
-128
],
"parameters": {
"text": "=You are analyzing competitor ad messaging changes between two weeks.\n\nCOMPETITOR: {{ $json.competitor_name }} ({{ $json.domain }})\nWEEK BEING ANALYZED: {{ $json.week_of }}\nCOMPARED TO: {{ $json.previous_week }}\n\n---\nLAST WEEK'S ADS:\n{{ $json.last_week_text }}\n\n---\nTHIS WEEK'S ADS:\n{{ $json.this_week_text }}\n\n---\nNEW AD TITLES THIS WEEK (not seen last week):\n{{ $json.new_titles.join(', ') || 'None' }}\n\nDROPPED AD TITLES (ran last week, gone this week):\n{{ $json.dropped_titles.join(', ') || 'None' }}\n\n---\nAnalyze the shift in messaging strategy between these two weeks.\n\nReturn ONLY this raw JSON object with no markdown, no backticks, no explanation:\n{\"angle_shift\":\"one sentence describing any pivot in core message angle, or No significant change detected\",\"new_audiences\":\"comma-separated audience segments targeted by NEW ads, or Same as prior week\",\"new_value_props\":\"comma-separated new value propositions or features mentioned, or Same as prior week\",\"urgency_signal\":\"High or Medium or Low\",\"urgency_reason\":\"one sentence explaining the urgency rating\",\"delta_summary\":\"2-3 sentence plain-English summary of what changed and what it likely signals strategically\",\"new_ad_titles\":\"semicolon-separated list of new ad titles, or None\",\"dropped_ad_titles\":\"semicolon-separated list of dropped ad titles, or None\"}",
"options": {
"systemMessage": "You are a competitive intelligence analyst specializing in paid advertising messaging strategy.\n\nYour job is to detect meaningful shifts in a competitor's ad messaging week over week.\n\nFocus on: angle changes (feature vs benefit vs social proof vs fear vs urgency), audience targeting shifts (SMB vs enterprise, job titles, verticals), new product or feature names surfacing, value proposition changes, and tone shifts.\n\nIf it is the first run and there is no previous week data, analyze only this week's ads and describe the current messaging strategy.\n\nReturn ONLY a raw JSON object. No markdown. No backticks. No code fences. No explanation before or after. If you cannot determine a value, use the string NA."
},
"promptType": "define"
},
"typeVersion": 3.1
},
{
"id": "6479c273-f886-41e0-9d76-fc5e630e80f2",
"name": "OpenAI Chat Model",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"position": [
1312,
96
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "gpt-4.1-mini",
"cachedResultName": "gpt-4.1-mini"
},
"options": {
"temperature": 0.2
},
"responsesApiEnabled": false
},
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "f9fe9666-c3f4-46e8-ad67-ac86fcac8369",
"name": "Parse Delta Result",
"type": "n8n-nodes-base.code",
"position": [
1584,
-32
],
"parameters": {
"jsCode": "// ===== PARSE AI OUTPUT \u2014 HANDLES JSON AND PLAIN TEXT =====\n\nconst items = $input.all();\nconst data = items[0].json;\n\nlet aiResult = {\n angle_shift: 'N/A',\n new_audiences: 'N/A',\n new_value_props: 'N/A',\n urgency_signal: 'N/A',\n urgency_reason: 'N/A',\n delta_summary: 'N/A',\n new_ad_titles: 'N/A',\n dropped_ad_titles: 'N/A'\n};\n\nlet rawText = '';\nif (typeof data.output === 'string') rawText = data.output;\nelse if (typeof data.text === 'string') rawText = data.text;\nelse if (typeof data === 'string') rawText = data;\nelse rawText = JSON.stringify(data);\n\n// Attempt 1: parse as JSON\nlet parsedFromJson = false;\ntry {\n const cleaned = rawText\n .replace(/```json\\s*/gi, '')\n .replace(/```\\s*/gi, '')\n .trim();\n const start = cleaned.indexOf('{');\n const end = cleaned.lastIndexOf('}');\n if (start !== -1 && end !== -1) {\n const parsed = JSON.parse(cleaned.substring(start, end + 1));\n // Validate it has at least one real field\n if (parsed.delta_summary || parsed.angle_shift) {\n aiResult.angle_shift = parsed.angle_shift || 'N/A';\n aiResult.new_audiences = parsed.new_audiences || 'N/A';\n aiResult.new_value_props = parsed.new_value_props || 'N/A';\n aiResult.urgency_signal = parsed.urgency_signal || 'N/A';\n aiResult.urgency_reason = parsed.urgency_reason || 'N/A';\n aiResult.delta_summary = parsed.delta_summary || 'N/A';\n aiResult.new_ad_titles = parsed.new_ad_titles || 'N/A';\n aiResult.dropped_ad_titles = parsed.dropped_ad_titles || 'N/A';\n parsedFromJson = true;\n }\n }\n} catch (e) {}\n\n// Attempt 2: regex field extraction\nif (!parsedFromJson) {\n function extract(text, field) {\n const re = new RegExp('\"' + field + '\"\\\\s*:\\\\s*\"([^\"]+)\"');\n const m = text.match(re);\n return m ? m[1] : null;\n }\n aiResult.angle_shift = extract(rawText, 'angle_shift') || aiResult.angle_shift;\n aiResult.new_audiences = extract(rawText, 'new_audiences') || aiResult.new_audiences;\n aiResult.new_value_props = extract(rawText, 'new_value_props') || aiResult.new_value_props;\n aiResult.urgency_signal = extract(rawText, 'urgency_signal') || aiResult.urgency_signal;\n aiResult.urgency_reason = extract(rawText, 'urgency_reason') || aiResult.urgency_reason;\n aiResult.delta_summary = extract(rawText, 'delta_summary') || aiResult.delta_summary;\n aiResult.new_ad_titles = extract(rawText, 'new_ad_titles') || aiResult.new_ad_titles;\n aiResult.dropped_ad_titles = extract(rawText, 'dropped_ad_titles') || aiResult.dropped_ad_titles;\n\n // Attempt 3: plain-text fallback \u2014 use the whole output as delta_summary\n if (aiResult.delta_summary === 'N/A' && rawText.length > 10) {\n aiResult.delta_summary = rawText.trim().substring(0, 400);\n\n // Try to detect urgency from keywords\n const lower = rawText.toLowerCase();\n if (lower.includes('significant') || lower.includes('pivot') || lower.includes('major') || lower.includes('new product') || lower.includes('launch')) {\n aiResult.urgency_signal = 'High';\n } else if (lower.includes('minor') || lower.includes('slight') || lower.includes('small')) {\n aiResult.urgency_signal = 'Low';\n } else {\n aiResult.urgency_signal = 'Medium';\n }\n }\n}\n\n// Get comparison context from Build Comparison Context node\nlet compData = {};\ntry {\n compData = $('Build Comparison Context').first().json;\n} catch(e) {}\n\nconst domain = compData.domain || '';\nconst competitorName = compData.competitor_name || '';\nconst weekOf = compData.week_of || '';\nconst previousWeek = compData.previous_week || '';\nconst newTitles = (compData.new_titles || []).join('; ') || 'None';\nconst droppedTitles = (compData.dropped_titles || []).join('; ') || 'None';\n\nreturn [{\n json: {\n domain: domain,\n competitor_name: competitorName,\n week_of: weekOf,\n previous_week: previousWeek,\n new_ad_titles: aiResult.new_ad_titles !== 'N/A' ? aiResult.new_ad_titles : newTitles,\n dropped_ad_titles: aiResult.dropped_ad_titles !== 'N/A' ? aiResult.dropped_ad_titles : droppedTitles,\n angle_shift: aiResult.angle_shift,\n new_audiences: aiResult.new_audiences,\n new_value_props: aiResult.new_value_props,\n urgency_signal: aiResult.urgency_signal,\n urgency_reason: aiResult.urgency_reason,\n delta_summary: aiResult.delta_summary,\n analyzed_at: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "db94f104-66a0-4f4c-983b-47618558bbb4",
"name": "Append No-Ads Delta1",
"type": "n8n-nodes-base.googleSheets",
"position": [
1808,
-32
],
"parameters": {
"columns": {
"value": {
"domain": "={{ $json.domain }}",
"week_of": "={{ $json.week_of }}",
"analyzed_at": "={{ $json.analyzed_at }}",
"angle_shift": "={{ $json.angle_shift }}",
"delta_summary": "={{ $json.delta_summary }}",
"new_ad_titles": "={{ $json.new_ad_titles }}",
"new_audiences": "={{ $json.new_audiences }}",
"previous_week": "={{ $json.previous_week }}",
"urgency_signal": "={{ $json.urgency_signal }}",
"competitor_name": "={{ $json.competitor_name }}",
"new_value_props": "={{ $json.new_value_props }}",
"dropped_ad_titles": "={{ $json.dropped_ad_titles }}"
},
"schema": [
{
"id": "domain",
"type": "string",
"display": true,
"required": false,
"displayName": "domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "competitor_name",
"type": "string",
"display": true,
"required": false,
"displayName": "competitor_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "week_of",
"type": "string",
"display": true,
"required": false,
"displayName": "week_of",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "previous_week",
"type": "string",
"display": true,
"required": false,
"displayName": "previous_week",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "new_ad_titles",
"type": "string",
"display": true,
"required": false,
"displayName": "new_ad_titles",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "dropped_ad_titles",
"type": "string",
"display": true,
"required": false,
"displayName": "dropped_ad_titles",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "angle_shift",
"type": "string",
"display": true,
"required": false,
"displayName": "angle_shift",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "new_audiences",
"type": "string",
"display": true,
"required": false,
"displayName": "new_audiences",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "new_value_props",
"type": "string",
"display": true,
"required": false,
"displayName": "new_value_props",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "urgency_signal",
"type": "string",
"display": true,
"required": false,
"displayName": "urgency_signal",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "delta_summary",
"type": "string",
"display": true,
"required": false,
"displayName": "delta_summary",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "analyzed_at",
"type": "string",
"display": true,
"required": false,
"displayName": "analyzed_at",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1875674687,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit#gid=1875674687",
"cachedResultName": "messaging_deltas"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit?usp=drivesdk",
"cachedResultName": "4th template"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "5cb9e900-a44c-4644-a725-229e06aa63f6",
"name": "Prepare Snapshot Rows",
"type": "n8n-nodes-base.code",
"position": [
2032,
-32
],
"parameters": {
"jsCode": "// ===== PREPARE SNAPSHOT ROWS FOR WRITING =====\n// Runs AFTER delta is saved \u2014 this becomes next week's 'last week'\n\nlet thisWeekRows = [];\ntry {\n const compData = $('Build Comparison Context').first().json;\n thisWeekRows = compData.this_week_rows || [];\n} catch(e) {\n return [{ json: { _skip: true, error: 'Could not read this_week_rows' } }];\n}\n\nif (thisWeekRows.length === 0) {\n return [{ json: { _skip: true } }];\n}\n\nreturn thisWeekRows.map(r => ({ json: r }));"
},
"typeVersion": 2
},
{
"id": "bfb85221-a576-4231-acdf-e666fc6f22d4",
"name": "Append to Snapshots",
"type": "n8n-nodes-base.googleSheets",
"position": [
2256,
-32
],
"parameters": {
"columns": {
"value": {
"domain": "={{ $json.domain }}",
"ad_body": "={{ $json.ad_body }}",
"week_of": "={{ $json.week_of }}",
"ad_title": "={{ $json.ad_title }}",
"platform": "={{ $json.platform }}",
"captured_at": "={{ $json.captured_at }}",
"snapshot_id": "={{ $json.snapshot_id }}",
"competitor_name": "={{ $json.competitor_name }}"
},
"schema": [
{
"id": "snapshot_id",
"type": "string",
"display": true,
"required": false,
"displayName": "snapshot_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "domain",
"type": "string",
"display": true,
"required": false,
"displayName": "domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "competitor_name",
"type": "string",
"display": true,
"required": false,
"displayName": "competitor_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "week_of",
"type": "string",
"display": true,
"required": false,
"displayName": "week_of",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "platform",
"type": "string",
"display": true,
"required": false,
"displayName": "platform",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ad_title",
"type": "string",
"display": true,
"required": false,
"displayName": "ad_title",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "ad_body",
"type": "string",
"display": true,
"required": false,
"displayName": "ad_body",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "captured_at",
"type": "string",
"display": true,
"required": false,
"displayName": "captured_at",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": 1910720088,
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit#gid=1910720088",
"cachedResultName": "ad_copy_snapshots"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit?usp=drivesdk",
"cachedResultName": "4th template"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "786b22df-2225-444e-8762-d6f3a72cc7e8",
"name": "Urgency = High?",
"type": "n8n-nodes-base.if",
"position": [
2688,
-32
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "d36de004-4c82-4075-b16c-8db80128f9ba",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.urgency_signal }}",
"rightValue": "=High"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "726c638b-878c-4543-8a2f-6b06fcefed68",
"name": "Format Slack Alert",
"type": "n8n-nodes-base.code",
"position": [
2880,
-80
],
"parameters": {
"jsCode": "// ===== FORMAT SLACK ALERT =====\n\nconst data = $input.first().json;\n\n// Read delta result \u2014 check both Parse Delta Result and fallback\nlet deltaData = data;\ntry {\n deltaData = $('Parse Delta Result').first().json;\n} catch(e) {}\n\nconst urgencyEmoji = {\n 'High': '\ud83d\udd34',\n 'Medium': '\ud83d\udfe1',\n 'Low': '\ud83d\udfe2'\n};\n\nconst emoji = urgencyEmoji[deltaData.urgency_signal] || '\u26aa';\n\nconst message = [\n `${emoji} *Competitor Messaging Alert* \u2014 ${deltaData.urgency_signal} Urgency`,\n `*Competitor:* ${deltaData.competitor_name} (${deltaData.domain})`,\n `*Week:* ${deltaData.week_of} vs ${deltaData.previous_week}`,\n ``,\n `*What changed:* ${deltaData.delta_summary}`,\n ``,\n `*Angle shift:* ${deltaData.angle_shift}`,\n `*New audiences:* ${deltaData.new_audiences}`,\n `*New value props:* ${deltaData.new_value_props}`,\n ``,\n `*New ads:* ${deltaData.new_ad_titles}`,\n `*Dropped ads:* ${deltaData.dropped_ad_titles}`\n].join('\\n');\n\nreturn [{\n json: {\n slack_message: message,\n competitor_name: deltaData.competitor_name,\n urgency_signal: deltaData.urgency_signal\n }\n}];"
},
"typeVersion": 2
},
{
"id": "f83797c9-a9f0-48f4-bd53-c568e84339da",
"name": "Build No-Ads Delta Row",
"type": "n8n-nodes-base.code",
"position": [
1296,
320
],
"parameters": {
"jsCode": "// ===== BUILD NO-ADS DELTA ROW =====\n// When a competitor had no ads this week, log it as a Low urgency delta\n\nconst data = $input.first().json;\n\nlet compData = {};\ntry {\n compData = $('Build Comparison Context').first().json;\n} catch(e) {}\n\nconst domain = compData.domain || data.domain || '';\nconst competitorName = compData.competitor_name || data.competitor_name || '';\nconst weekOf = compData.week_of || data.week_of || '';\nconst previousWeek = compData.previous_week || '';\n\nreturn [{\n json: {\n domain: domain,\n competitor_name: competitorName,\n week_of: weekOf,\n previous_week: previousWeek,\n new_ad_titles: 'None',\n dropped_ad_titles: 'All ads \u2014 no active ads found this week',\n angle_shift: 'No active ads detected this week',\n new_audiences: 'N/A',\n new_value_props: 'N/A',\n urgency_signal: 'Medium',\n urgency_reason: 'Competitor has gone dark \u2014 potential budget pause or campaign reset',\n delta_summary: `${competitorName} had no detectable active ads this week across Meta, Google, and LinkedIn. This could indicate a budget pause, campaign reset, or preparation for a new launch.`,\n analyzed_at: new Date().toISOString()\n }\n}];"
},
"typeVersion": 2
},
{
"id": "b4ffce00-679f-41db-92a8-5eb74d027212",
"name": "Append No-Ads Delta",
"type": "n8n-nodes-base.googleSheets",
"position": [
1584,
320
],
"parameters": {
"columns": {
"value": {
"domain": "={{ $json.domain }}",
"week_of": "={{ $json.week_of }}",
"analyzed_at": "={{ $json.analyzed_at }}",
"angle_shift": "={{ $json.angle_shift }}",
"delta_summary": "={{ $json.delta_summary }}",
"new_ad_titles": "={{ $json.new_ad_titles }}",
"new_audiences": "={{ $json.new_audiences }}",
"previous_week": "={{ $json.previous_week }}",
"urgency_reason": "={{ $json.urgency_reason }}",
"urgency_signal": "={{ $json.urgency_signal }}",
"competitor_name": "={{ $json.competitor_name }}",
"new_value_props": "={{ $json.new_value_props }}",
"dropped_ad_titles": "={{ $json.dropped_ad_titles }}"
},
"schema": [
{
"id": "domain",
"type": "string",
"display": true,
"required": false,
"displayName": "domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "competitor_name",
"type": "string",
"display": true,
"required": false,
"displayName": "competitor_name",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "week_of",
"type": "string",
"display": true,
"required": false,
"displayName": "week_of",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "previous_week",
"type": "string",
"display": true,
"required": false,
"displayName": "previous_week",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "new_ad_titles",
"type": "string",
"display": true,
"required": false,
"displayName": "new_ad_titles",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "dropped_ad_titles",
"type": "string",
"display": true,
"required": false,
"displayName": "dropped_ad_titles",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "angle_shift",
"type": "string",
"display": true,
"required": false,
"displayName": "angle_shift",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "new_audiences",
"type": "string",
"display": true,
"required": false,
"displayName": "new_audiences",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "new_value_props",
"type": "string",
"display": true,
"required": false,
"displayName": "new_value_props",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "urgency_signal",
"type": "string",
"display": true,
"required": false,
"displayName": "urgency_signal",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "urgency_reason",
"type": "string",
"display": true,
"required": false,
"displayName": "urgency_reason",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "delta_summary",
"type": "string",
"display": true,
"required": false,
"displayName": "delta_summary",
"defaultMatch": false,
"canBeUsedToMatch": false
},
{
"id": "analyzed_at",
"type": "string",
"display": true,
"required": false,
"displayName": "analyzed_at",
"defaultMatch": false,
"canBeUsedToMatch": false
}
],
"mappingMode": "defineBelow",
"matchingColumns": [],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "append",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "messaging_deltas"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit?usp=drivesdk",
"cachedResultName": "4th template"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.4
},
{
"id": "5c51f29c-dd36-4a10-816d-7d753a052aa2",
"name": "Update competitor status",
"type": "n8n-nodes-base.googleSheets",
"position": [
2464,
-32
],
"parameters": {
"columns": {
"value": {
"status": "Done",
"row_number": "={{ $('Loop Over Competitors').item.json.row_number }}"
},
"schema": [
{
"id": "competitor_name",
"type": "string",
"display": true,
"required": false,
"displayName": "competitor_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "domain",
"type": "string",
"display": true,
"required": false,
"displayName": "domain",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": false,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"row_number"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "list",
"value": "gid=0",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit#gid=0",
"cachedResultName": "competitors"
},
"documentId": {
"__rl": true,
"mode": "list",
"value": "1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g",
"cachedResultUrl": "https://docs.google.com/spreadsheets/d/1nx-31F_EMPMPEIEUM1W8Acd4r4ZLXjVTOd94rUN9i4g/edit?usp=drivesdk",
"cachedResultName": "4th template"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "b1485500-5fd9-4ac7-bc2b-11b7e5df433b",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-2096,
-288
],
"parameters": {
"width": 784,
"height": 736,
"content": "# \ud83d\ude80 Messaging Delta Tracker\n\n## \ud83d\udd0d How it works\n\nTracks competitor advertising activity across Meta, Google, and LinkedIn to detect messaging changes and creative shifts over time. The workflow collects ads weekly, extracts and normalizes ad copy, merges multi-platform data, and builds a structured snapshot for competitor messaging comparison.\n\n## \u2699\ufe0f How to set up\n- [ ] Connect Google Sheets credentials\n- [ ] Configure Adyntel API credentials\n- [ ] Ensure competitor domains are correctly stored in the sheet\n- [ ] - [ ] Set competitor status to Pending\n- [ ] Enable the weekly schedule trigger\n- [ ] Verify API access for Meta, Google, and LinkedIn ad sources\n- [ ] Test with a small set of competitors\n\n## \ud83d\udee0\ufe0f Customization\n- Change execution frequency (daily, weekly, monthly)\n- Add/remove advertising platforms (TikTok, Twitter/X, etc.)\n- Modify extraction logic for ad copy quality improvements\n- Enhance deduplication rules for cleaner datasets\n- Add AI layer for messaging analysis and insights\n- Send results to Slack, Notion, Airtable, or dashboards"
},
"typeVersion": 1
},
{
"id": "3a8f4809-de8b-418f-95b0-a8d358d73e38",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1200,
-80
],
"parameters": {
"color": 6,
"width": 624,
"height": 352,
"content": "## \ud83d\udcc5 Workflow Trigger & Competitor Processing\n\nPurpose\nStarts the workflow on a weekly schedule, fetches competitors marked as Pending from Google Sheets, and processes each competitor one at a time using batch looping."
},
"typeVersion": 1
},
{
"id": "ff8ca875-5171-4ec5-87a6-275bf40cb84d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-400,
-272
],
"parameters": {
"color": 5,
"width": 1312,
"height": 752,
"content": "## \ud83d\udce2 Multi-Platform Ad Collection\n\nPurpose\nCollects advertising data from Meta, Google, and LinkedIn using competitor domains, then merges all platform outputs into a single unified dataset for further processing."
},
"typeVersion": 1
},
{
"id": "806b5051-6f56-4102-9ae8-957ebd81d0b3",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
976,
-304
],
"parameters": {
"color": 4,
"width": 544,
"height": 768,
"content": "## \ud83d\udcca Messaging Delta Analysis (AI Core)\n\nPurpose\nAnalyzes week-over-week changes in competitor messaging using AI. Detects new angles, audience shifts, urgency changes, and strategic pivots. Handles edge cases where no ads are found."
},
"typeVersion": 1
},
{
"id": "1aeb57dd-dcfa-4a6c-82eb-3dd813405d5b",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1536,
-256
],
"parameters": {
"color": 2,
"width": 1440,
"height": 768,
"content": "## \ud83d\uddc2\ufe0f Storage, Alerts & Loop Completion\nPurpose\nStores processed ad snapshots and messaging deltas in Google Sheets, triggers Slack alerts for high urgency changes, and ensures continuous loop execution across all competitors."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "c91c73bc-63a3-443f-b05a-3d26e0a1fcd7",
"connections": {
"Urgency = High?": {
"main": [
[
{
"node": "Format Slack Alert",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop Over Competitors",
"type": "main",
"index": 0
}
]
]
},
"Read Competitors": {
"main": [
[
{
"node": "Loop Over Competitors",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Read Competitors",
"type": "main",
"index": 0
}
]
]
},
"OpenAI Chat Model": {
"ai_languageModel": [
[
{
"node": "AI Agent \u2014 Detect Messaging Delta",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"Search Google Ads": {
"main": [
[
{
"node": "Merge All Platform Results",
"type": "main",
"index": 1
}
]
]
},
"Format Slack Alert": {
"main": [
[
{
"node": "Loop Over Competitors",
"type": "main",
"index": 0
}
]
]
},
"Has Ads This Week?": {
"main": [
[
{
"node": "Build No-Ads Delta Row",
"type": "main",
"index": 0
}
],
[
{
"node": "AI Agent \u2014 Detect Messaging Delta",
"type": "main",
"index": 0
}
]
]
},
"Parse Delta Result": {
"main": [
[
{
"node": "Append No-Ads Delta1",
"type": "main",
"index": 0
}
]
]
},
"Append No-Ads Delta": {
"main": [
[
{
"node": "Loop Over Competitors",
"type": "main",
"index": 0
}
]
]
},
"Append to Snapshots": {
"main": [
[
{
"node": "Update competitor status",
"type": "main",
"index": 0
}
]
]
},
"Search Facebook Ads": {
"main": [
[
{
"node": "Merge All Platform Results",
"type": "main",
"index": 0
}
]
]
},
"Search LinkedIn Ads": {
"main": [
[
{
"node": "Merge All Platform Results",
"type": "main",
"index": 2
}
]
]
},
"Append No-Ads Delta1": {
"main": [
[
{
"node": "Prepare Snapshot Rows",
"type": "main",
"index": 0
}
]
]
},
"Loop Over Competitors": {
"main": [
[],
[
{
"node": "Search Facebook Ads",
"type": "main",
"index": 0
},
{
"node": "Search Google Ads",
"type": "main",
"index": 0
},
{
"node": "Search LinkedIn Ads",
"type": "main",
"index": 0
}
]
]
},
"Prepare Snapshot Rows": {
"main": [
[
{
"node": "Append to Snapshots",
"type": "main",
"index": 0
}
]
]
},
"Build No-Ads Delta Row": {
"main": [
[
{
"node": "Append No-Ads Delta",
"type": "main",
"index": 0
}
]
]
},
"Build Comparison Context": {
"main": [
[
{
"node": "Has Ads This Week?",
"type": "main",
"index": 0
}
]
]
},
"Extract This Week's Copy": {
"main": [
[
{
"node": "Aggregate This Week's Copy",
"type": "main",
"index": 0
}
]
]
},
"Read Last Week Snapshots": {
"main": [
[
{
"node": "Build Comparison Context",
"type": "main",
"index": 0
}
]
]
},
"Update competitor status": {
"main": [
[
{
"node": "Urgency = High?",
"type": "main",
"index": 0
}
]
]
},
"Aggregate This Week's Copy": {
"main": [
[
{
"node": "Read Last Week Snapshots",
"type": "main",
"index": 0
}
]
]
},
"Merge All Platform Results": {
"main": [
[
{
"node": "Extract This Week's Copy",
"type": "main",
"index": 0
}
]
]
},
"AI Agent \u2014 Detect Messaging Delta": {
"main": [
[
{
"node": "Parse Delta Result",
"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.
adyntelApigoogleSheetsOAuth2ApiopenAiApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This scheduled workflow reads competitor domains from Google Sheets, pulls their current ads from Meta, Google, and LinkedIn via Adyntel, compares this week’s messaging to last week’s snapshot with OpenAI, stores results back in Google Sheets, and sends Slack alerts for…
Source: https://n8n.io/workflows/16288/ — 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.
This scheduled workflow scans competitor ads via Adyntel (Meta, Google, and LinkedIn), extracts newly appearing terms from ad copy, and uses OpenAI to classify potential launch signals, then logs resu
⚠️ DISCLAIMER: This workflow uses the AnySite LinkedIn community node, which is only available on self-hosted n8n instances. It will not work on n8n.cloud.
This n8n automation workflow automates the creation, scripting, production, and posting of YouTube videos. It leverages AI (OpenAI), image generation (PIAPI), video rendering (Shotstack), and platform
This workflow is designed for: Content creators and marketers E-commerce and product-based businesses Agencies producing social media visuals and videos Automation builders looking for AI-powered crea
Generate product images with NanoBanana Pro to Veo videos and Blotato - vide 2 ok. Uses httpRequest, editImage, googleDrive, googleSheets. Scheduled trigger; 76 nodes.