This workflow corresponds to n8n.io template #11103 — we link there as the canonical source.
This workflow follows the HTTP Request → RSS Feed Read 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": "COew5sp75oHL2OGK",
"name": "Crypto/Stocks News Watcher",
"tags": [
{
"id": "BdxHwMUMr2WXfcxw",
"name": "twutter",
"createdAt": "2025-11-21T10:35:33.738Z",
"updatedAt": "2025-11-21T10:35:33.738Z"
},
{
"id": "DhyDfEdgkMYu0hEj",
"name": "news",
"createdAt": "2025-11-21T10:35:27.836Z",
"updatedAt": "2025-11-21T10:35:27.836Z"
},
{
"id": "PXrNe4uaUViL0EiW",
"name": "crypto",
"createdAt": "2025-11-21T10:35:19.757Z",
"updatedAt": "2025-11-21T10:35:19.757Z"
},
{
"id": "ho8Enj73YkWHSqa0",
"name": "feed",
"createdAt": "2025-11-21T10:35:35.200Z",
"updatedAt": "2025-11-21T10:35:35.200Z"
},
{
"id": "lRkv4ieCJFkbaiCU",
"name": "x",
"createdAt": "2025-11-21T10:35:31.050Z",
"updatedAt": "2025-11-21T10:35:31.050Z"
},
{
"id": "ovWAcv5DtL414Mhj",
"name": "stocks",
"createdAt": "2025-11-21T10:35:24.929Z",
"updatedAt": "2025-11-21T10:35:24.929Z"
},
{
"id": "yb7rVVJyQN19GgKt",
"name": "rss",
"createdAt": "2025-11-21T10:35:29.640Z",
"updatedAt": "2025-11-21T10:35:29.640Z"
}
],
"nodes": [
{
"id": "3605441c-09a7-408d-ae58-952fca2fbddf",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-1712,
-32
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 30
}
]
}
},
"typeVersion": 1.2
},
{
"id": "95f9c5dd-bed8-4ae7-afd0-f52735d9a721",
"name": "RSS Read - Coindesk",
"type": "n8n-nodes-base.rssFeedRead",
"position": [
-416,
-256
],
"parameters": {
"url": "https://www.coindesk.com/arc/outboundfeeds/rss/",
"options": {}
},
"executeOnce": true,
"typeVersion": 1.2
},
{
"id": "94b0094a-4e1d-4e02-bac6-d01b35fa3a89",
"name": "RSS Read - Google news",
"type": "n8n-nodes-base.rssFeedRead",
"position": [
-416,
-80
],
"parameters": {
"url": "={{$json.url}}",
"options": {}
},
"retryOnFail": true,
"typeVersion": 1.2
},
{
"id": "56b70dc1-cef6-4dea-9ea3-e974a33bd5f1",
"name": "RSS Read - X Posts",
"type": "n8n-nodes-base.rssFeedRead",
"position": [
-32,
64
],
"parameters": {
"url": "={{ $json.rssUrl }}",
"options": {}
},
"typeVersion": 1.2
},
{
"id": "1733a9d9-08ac-46bb-ab9b-9b7987320d51",
"name": "Source set - Coindesk",
"type": "n8n-nodes-base.set",
"position": [
1024,
-256
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "04f3aa10-f74d-48e6-bd8c-e63c017c5571",
"name": "source",
"type": "string",
"value": "CoinDesk"
},
{
"id": "39bd311b-dfa3-478f-beca-9e49012d9ffa",
"name": "kind",
"type": "string",
"value": "Article / News"
},
{
"id": "c22914b9-46fd-4de6-9f71-4b59a4fe93bc",
"name": "topic",
"type": "string",
"value": "={{$json.topic}}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "d535536a-a590-405e-b806-e14617d158bf",
"name": "Source set - Google news",
"type": "n8n-nodes-base.set",
"position": [
1024,
-80
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "04f3aa10-f74d-48e6-bd8c-e63c017c5571",
"name": "source",
"type": "string",
"value": "Google News"
},
{
"id": "39bd311b-dfa3-478f-beca-9e49012d9ffa",
"name": "kind",
"type": "string",
"value": "News"
},
{
"id": "55446164-5ddd-442c-be8d-5296569b2388",
"name": "topic",
"type": "string",
"value": "={{$json.topic}}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "0dcafdf0-9e39-4409-9c5d-54cae63010c1",
"name": "Source set - Cointelegraph",
"type": "n8n-nodes-base.set",
"position": [
1024,
432
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "04f3aa10-f74d-48e6-bd8c-e63c017c5571",
"name": "source",
"type": "string",
"value": "CoinTelegraph"
},
{
"id": "39bd311b-dfa3-478f-beca-9e49012d9ffa",
"name": "kind",
"type": "string",
"value": "Article / News"
},
{
"id": "e6e7b215-75a3-4e01-9f27-59b8c833a881",
"name": "topic",
"type": "string",
"value": "={{$json.topic}}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "e113b336-a53f-4054-94e7-6a1f86ea8e44",
"name": "Source set - X Posts",
"type": "n8n-nodes-base.set",
"position": [
1024,
240
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "04f3aa10-f74d-48e6-bd8c-e63c017c5571",
"name": "source",
"type": "string",
"value": "X"
},
{
"id": "39bd311b-dfa3-478f-beca-9e49012d9ffa",
"name": "kind",
"type": "string",
"value": "Tweet"
},
{
"id": "b3208846-d56e-4b3b-9f92-a0389d4fab77",
"name": "topic",
"type": "string",
"value": "={{$json.topic}}"
}
]
},
"includeOtherFields": true
},
"typeVersion": 3.4
},
{
"id": "8d5a498d-3c2a-40e8-ba90-456f400b382e",
"name": "Merge - Coindesk + Google news",
"type": "n8n-nodes-base.merge",
"position": [
1280,
-176
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "f711a07b-0bbe-4102-81f8-d11736ed796f",
"name": "Merge - X posts + CoinTelegraph",
"type": "n8n-nodes-base.merge",
"position": [
1280,
336
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "47e8fd90-a623-42a9-9637-55d9f262eceb",
"name": "Merge - Merge previous two merges",
"type": "n8n-nodes-base.merge",
"position": [
1568,
64
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "9a05a06c-e0e3-4c48-adc7-ff6c8f869cd1",
"name": "Code - Keywords Filter",
"type": "n8n-nodes-base.code",
"position": [
1872,
64
],
"parameters": {
"jsCode": "// ---------------- Config (topic-aware) ----------------\n\n// Tje Keywords filtering - Replace keywords as you want.\nconst KEEP_CRYPTO = [\n 'crypto','cryptocurrency','bitcoin','btc','ethereum','eth','market','markets','signals',\n 'selloff','sell-off','dump','dumped','liquidation','flash crash','$Grass','$grass','grass token',\n 'bullish','bearish','breakout','btcusd','etf','spot etf','hack','hacked','hacking','exploit','rug pull','rug',\n];\n\nconst KEEP_STOCKS = [\n 'stock','stocks','equities','market','markets','earnings','guidance','revenue','profit','loss',\n 'ipo','dividend','split','buyback','upgrade','downgrade','rating','price target','outlook','fomc',\n 'dow','nasdaq','s&p','spy','qqq',\n // common tickers / names\n 'nvda','nvidia','aapl','apple','tsla','tesla','msft','microsoft','amzn','amazon','meta','facebook','goog','googl','alphabet',\n];\n\nconst DROP = ['giveaway','airdrop','referral','signal group','win $','win$'];\nconst MAX_IMAGES = 4;\n\n// set true only while debugging to clear dedupe memory for this run\nconst CLEAR_MEMORY_THIS_RUN = false;\n\n// ---------------- Helpers ----------------\nconst wf = $getWorkflowStaticData('global');\nif (CLEAR_MEMORY_THIS_RUN) wf.seen = [];\nconst seen = new Set(wf.seen || []);\n\nconst esc = (s) => s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n\nconst buildKeepRE = (arr) =>\n arr.map((k) => {\n const pat = esc(k).replace(/\\\\\\s+/g, '\\\\s+'); // \"spot etf\" -> spot\\s+etf\n return [k, new RegExp(`(^|[^\\\\w])${pat}([^\\\\w]|$)`, 'i')];\n });\n\nconst KEEP_RE_CRYPTO = buildKeepRE(KEEP_CRYPTO);\nconst KEEP_RE_STOCKS = buildKeepRE(KEEP_STOCKS);\nconst DROP_RE = DROP.map((k) => {\n const pat = esc(k).replace(/\\\\\\s+/g, '\\\\s+');\n return new RegExp(`(^|[^\\\\w])${pat}([^\\\\w]|$)`, 'i');\n});\n\nconst normSource = (j, url) => {\n if (j.source) return j.source;\n const u = (url || '').toLowerCase();\n if (u.includes('coindesk')) return 'CoinDesk';\n if (u.includes('cointelegraph')) return 'CoinTelegraph';\n if (u.includes('news.google')) return 'Google News';\n if (u.includes('xcancel') || u.includes('/status/')) return 'X posts';\n return 'rss';\n};\n\nconst kindOf = (j, url) => {\n const src = (j.source || '').toLowerCase();\n const u = (url || '').toLowerCase();\n if (src.includes('x') || src.includes('tweet') || u.includes('/status/')) return 'tweet';\n return 'article';\n};\n\nconst extractImages = (html = '', enclosure) => {\n const out = [];\n if (enclosure?.url) out.push(enclosure.url);\n const re = /<img[^>]+src=[\"']([^\"']+)[\"']/ig;\n let m;\n while ((m = re.exec(html)) && out.length < MAX_IMAGES) out.push(m[1]);\n return [...new Set(out)];\n};\n\nconst canonicalFromGoogleNews = (item) => {\n const m = /<a[^>]+href=[\"']([^\"']+)[\"']/i.exec(item.content || '');\n return m ? m[1] : (item.link || item.guid || '');\n};\n\nconst canonicalUrl = (j) => {\n let u = j.link || j.guid || j.url || '';\n if ((u || '').includes('news.google.com')) u = canonicalFromGoogleNews(j);\n return u.replace(/^https?:\\/\\/(www\\.)?/i, '').replace(/#.*$/,'').trim();\n};\n\nconst textOf = (j) => {\n const raw = [j.title, j.contentSnippet, j.content].filter(Boolean).join(' ');\n return raw.replace(/[\\s\\u00A0]+/g, ' ').replace(/[#$]/g, ' ').trim();\n};\n\nconst matchedKeywords = (txt, keepList) => {\n const hits = [];\n for (const [label, re] of keepList) if (re.test(txt)) hits.push(label);\n return [...new Set(hits)];\n};\n\nconst isDropped = (txt) => DROP_RE.some((re) => re.test(txt));\n\n// --------- NEW: derive run-level topic & repair each item ----------\nconst fromInit = $item(0)?.$node?.[\"Init RunConfig\"]?.json?.topic;\nconst RUN_TOPIC = (() => {\n const raw = String($json.topic ?? fromInit ?? 'crypto').toLowerCase().trim();\n if (raw === 'stocks' || raw === 'stock') return 'stocks';\n return 'crypto';\n})();\n\n// make sure every incoming item has a good topic (fixes \"=\" or missing)\nconst input = $input.all().map(i => {\n const t = String(i.json.topic ?? '').toLowerCase().trim();\n const fixedTopic = t && t !== '=' ? t : RUN_TOPIC;\n return { ...i.json, topic: fixedTopic };\n});\n\n// ---------------- Main ----------------\nconst counts = { in: input.length, kept: 0, dropped: 0, perSource: {} };\nconst out = [];\n\nfor (const j of input) {\n const topic = String(j.topic || RUN_TOPIC).toLowerCase();\n const KEEP_RE = topic === 'stocks' ? KEEP_RE_STOCKS : KEEP_RE_CRYPTO;\n\n const urlRaw = j.link || j.guid || j.url || '';\n const url = (urlRaw || '').trim();\n const title = (j.title || '').trim();\n if (!url || !title) { counts.dropped++; continue; }\n\n const text = textOf(j);\n if (!text) { counts.dropped++; continue; }\n\n if (isDropped(text)) { counts.dropped++; continue; }\n\n const hits = matchedKeywords(text, KEEP_RE);\n if (!hits.length) { counts.dropped++; continue; }\n\n const keyOnly = canonicalUrl(j);\n if (!keyOnly) { counts.dropped++; continue; }\n\n // Deduplicate across runs, per topic\n const key = `${topic}:${keyOnly}`;\n if (seen.has(key)) { counts.dropped++; continue; }\n seen.add(key);\n\n const when = new Date(j.isoDate || j.pubDate || j.publishedAt || Date.now());\n\n const src = normSource(j, url);\n counts.perSource[src] = (counts.perSource[src] || 0) + 1;\n\n out.push({\n json: {\n id: url,\n source: src,\n kind: kindOf(j, url),\n title,\n url,\n publishedAt: when.toISOString(),\n matchedKeywords: hits.join(','), // same field as before\n summary: (j.contentSnippet || '').replace(/\\s+/g, ' ').trim(),\n html: j.content || '',\n media: extractImages(j.content, j.enclosure),\n topic, // ensured & sanitized\n },\n });\n}\n\ncounts.kept = out.length;\nconsole.log({ stats: counts });\n\n// keep some memory (cap length)\nwf.seen = Array.from(seen).slice(-500);\n\nreturn out;\n"
},
"typeVersion": 2
},
{
"id": "54cf0cbf-2d00-47d7-bc97-c98daef776b7",
"name": "Code - Array bind",
"type": "n8n-nodes-base.code",
"position": [
2080,
64
],
"parameters": {
"jsCode": "const items = $input.all().map(i => i.json);\nconst topic = String(\n items[0]?.topic ?? $item(0).$node[\"Init RunConfig\"].json.topic ?? 'crypto'\n).toLowerCase();\n\nreturn [{ json: { topic, items } }];\n"
},
"typeVersion": 2
},
{
"id": "a44d59a2-eedf-4d90-a6f2-a15a209d443f",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"position": [
-1712,
144
],
"parameters": {
"path": "run-workflow",
"options": {},
"httpMethod": "POST"
},
"typeVersion": 2.1
},
{
"id": "9382dfc1-2dfb-4244-83bd-2b9cc9a3dd8c",
"name": "Merge",
"type": "n8n-nodes-base.merge",
"position": [
-1504,
48
],
"parameters": {},
"typeVersion": 3.2
},
{
"id": "60c11872-6e80-4023-9d18-e17a7da4f230",
"name": "Init RunConfig",
"type": "n8n-nodes-base.code",
"position": [
-1328,
48
],
"parameters": {
"jsCode": "// Accept both schedule and webhook paths\nconst body = ($json.body ?? $json) || {}\n\nconst topic = body.topic ?? $json.topic ?? 'crypto'\nlet platforms = body.platforms\n\nif (!Array.isArray(platforms) || !platforms.length) {\n platforms = ['coindesk', 'google', 'cointelegraph', 'x']\n}\n\nplatforms = platforms.map((s) => String(s).toLowerCase().trim())\n\n// simple query presets per topic\n//this is where you set your queries, replace to whatever your query is\n//XCancel does not provide a very long RSS Feed, so make sure not to include long set of queries OR Find a better alternative RSS provider that allows this.\nconst queries = topic === 'crypto' \n ? {\n google: '(bitcoin OR ethereum OR crypto OR blockchain)',\n x: '(bitcoin OR crypto OR BTC OR ETH OR solana OR SOL)'\n }\n : {\n google: '(stock OR stocks OR equities OR earnings OR market)',\n x: '(stocks OR stock OR SPY OR QQQ OR NVDA OR AAPL OR TSLA OR MSFT)'\n }\n\nreturn [{ json: { topic, platforms, queries } }]\n"
},
"typeVersion": 2
},
{
"id": "6713c172-8117-43be-8bdb-66e5ae755a0c",
"name": "IF Gate - Coindesk",
"type": "n8n-nodes-base.if",
"position": [
-848,
-240
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "7e97ad2e-60ef-4798-851c-e8e082905444",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ Array.isArray($json.platforms) && $json.topic === 'crypto' && $json.platforms.includes('coindesk') }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "2d94beb0-fc90-4f12-8ffa-8b1ec7565429",
"name": "IF Gate - Google news",
"type": "n8n-nodes-base.if",
"position": [
-848,
-64
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "7e97ad2e-60ef-4798-851c-e8e082905444",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ Array.isArray($json.platforms) && $json.platforms.includes('google') }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "a2378244-cd10-4848-a0c4-b7526ecd5a31",
"name": "IF Gate - CoinTelegraph",
"type": "n8n-nodes-base.if",
"position": [
-848,
448
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "7e97ad2e-60ef-4798-851c-e8e082905444",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ Array.isArray($json.platforms) && $json.topic === 'crypto' && $json.platforms.includes('cointelegraph') }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "de6fe9fb-f1a0-4119-b0bf-189d45e3ff98",
"name": "IF Gate - X",
"type": "n8n-nodes-base.if",
"position": [
-848,
160
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "7e97ad2e-60ef-4798-851c-e8e082905444",
"operator": {
"type": "boolean",
"operation": "true",
"singleValue": true
},
"leftValue": "={{ Array.isArray($json.platforms) && $json.platforms.includes('x') }}",
"rightValue": ""
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "1615f08c-e635-47f1-893e-10c7ff7fe3b7",
"name": "X Batches",
"type": "n8n-nodes-base.splitInBatches",
"position": [
-320,
144
],
"parameters": {
"options": {}
},
"typeVersion": 3
},
{
"id": "e0000def-80a6-448e-b416-afe90ba8eb55",
"name": "IF - More X batches?",
"type": "n8n-nodes-base.if",
"position": [
512,
224
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "8adddbbc-6b29-4cd6-a1c5-12b37e171b43",
"operator": {
"type": "boolean",
"operation": "false",
"singleValue": true
},
"leftValue": "=={{$json.hasMore}}",
"rightValue": "=={{$json.batchCount - 1}}"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.2
},
{
"id": "eae176c5-2d96-4a78-86eb-9868cbc47488",
"name": "Code - Finalize X batches (emit combined)",
"type": "n8n-nodes-base.code",
"position": [
704,
240
],
"parameters": {
"jsCode": "// Finalize X batches (emit combined)\nconst mem = $getWorkflowStaticData('global')\nconst all = Array.isArray(mem.x_items) ? mem.x_items : []\n// Emit as normal n8n items:\nreturn all.map(j => ({ json: j }))\n"
},
"typeVersion": 2
},
{
"id": "f835fc48-9583-4ba8-9d92-0a7e91574586",
"name": "Code - Accumulate X items",
"type": "n8n-nodes-base.code",
"position": [
304,
64
],
"parameters": {
"jsCode": "const mem = $getWorkflowStaticData('global');\n\n// all RSS items produced for this batch\nconst batch = $input.all().map(i => i.json);\n\n// append to the accumulator\nmem.x_items = (mem.x_items ?? []).concat(batch);\n\n// read the metadata written by \u201cSet batch metadata\u201d\nconst batchIndex = Number(mem.batchIndex ?? 0);\nconst batchCount = Number(mem.batchCount ?? 0);\nconst hasMore = batchIndex < (batchCount - 1);\n\n// emit a single control item that drives the IF node\nreturn [{\n json: {\n added: batch.length,\n total: mem.x_items.length,\n batchIndex,\n batchCount,\n hasMore,\n },\n}];\n"
},
"typeVersion": 2
},
{
"id": "48e24f9b-7f97-42f9-88ff-2eaafaead490",
"name": "Code - Reset X accumulator",
"type": "n8n-nodes-base.code",
"position": [
-496,
144
],
"parameters": {
"jsCode": "// Start a fresh buffer for this execution.\nconst mem = $getWorkflowStaticData('global') // \u2705 valid contexts: 'global' | 'node'\nmem.x_items = [] // where we'll collect all RSS items from each batch\nmem.x_topic = $json.topic // optional: remember topic ('crypto' | 'stocks') from builder\n\nreturn $input.all() // pass through\n"
},
"retryOnFail": true,
"typeVersion": 2
},
{
"id": "b306e15c-b6d8-4116-ae30-a5d3140d64b7",
"name": "Code - URL Build - XCancel",
"type": "n8n-nodes-base.code",
"position": [
-640,
144
],
"parameters": {
"jsCode": "// Build Xcancel URLs (batched, short queries)\n// topic comes from Init RunConfig (crypto | stocks)\n// optional: $json.tickers = [\"NVDA\",\"AAPL\",...] when topic === 'stocks'\n\nconst topic = String($json.topic ?? 'crypto').toLowerCase();\n\n// ----- Tunables you can tweak -----\nconst MAX_TICKERS_PER = 4; // <= keep small\nconst MAX_NEWS_PER = 3; // <= keep small\nconst MAX_PER_BATCH_CRYPTO = 6; // crypto keywords per query\nconst MIN_FAVES = 20;\nconst MIN_RTS = 5;\n// Optional length guard for extra safety (after quoting, before encoding)\nconst MAX_QUERY_CHARS = 420;\n// ----------------------------------\n\nconst filters =\n `lang:en -is:retweet -is:reply -is:quote filter:links min_faves:${MIN_FAVES} min_retweets:${MIN_RTS}`;\n\nconst toOR = (terms) =>\n '(' +\n terms\n .filter(Boolean)\n // quote if it has a space, a $ (cashtag), or any non-word char (e.g. M&A, S&P)\n .map((t) => (/[^\\w]/.test(t) ? `\"${t}\"` : t))\n .join(' OR ') +\n ')';\n\nconst toUrl = (q) =>\n 'https://xcancel.com/search/rss?f=tweets&q=' +\n encodeURIComponent(q.replace(/\\s+/g, ' ').trim());\n\nconst chunk = (arr, size) => {\n const out = [];\n for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));\n return out;\n};\n\nlet batches = [];\n\nif (topic === 'stocks') {\n // ----- STOCKS -----\n const tickers = Array.isArray($json.tickers) && $json.tickers.length\n ? $json.tickers\n : [\n 'AAPL','MSFT','NVDA','AMZN','GOOGL','META','TSLA','AMD','AVGO',\n 'NFLX','TSM','JPM','BAC','WMT','ORCL',\n ];\n const cashtags = tickers.map((t) => `$${String(t).toUpperCase()}`);\n\n const news = [\n 'earnings','EPS','guidance','revenue','outlook','forecast',\n 'upgrade','downgrade','price target','PT',\n 'dividend','buyback','merger','acquisition','M&A','IPO',\n 'halt','after hours','pre-market',\n ];\n\n const cashtagGroups = chunk(cashtags, MAX_TICKERS_PER);\n const newsGroups = chunk(news, MAX_NEWS_PER);\n\n // Pair small cashtag groups with small news groups: (tickers) AND (news)\n // This keeps each query short and highly relevant.\n for (const tg of cashtagGroups) {\n for (const ng of newsGroups) {\n let q = `${toOR(tg)} ${toOR(ng)} ${filters}`;\n // Safety: if still long, split news group to singles\n if (q.length > MAX_QUERY_CHARS) {\n for (const single of ng) {\n const q2 = `${toOR(tg)} ${toOR([single])} ${filters}`;\n batches.push({ rssUrl: toUrl(q2), queryReadable: q2 });\n }\n continue;\n }\n batches.push({ rssUrl: toUrl(q), queryReadable: q });\n }\n }\n\n // (Optional) add a very small set of pure-market queries (no cashtag) if you like:\n // for (const ng of newsGroups) {\n // const q = `${toOR(ng)} (${['stocks','equities','market'].join(' OR ')}) ${filters}`;\n // batches.push({ rssUrl: toUrl(q), queryReadable: q });\n // }\n} else {\n // ----- CRYPTO -----\n const cryptoTerms = [\n 'bitcoin','btc','spot ETF','flash crash','liquidation','btcusd','breakout',\n 'ethereum','eth','etf','rug pull','hack','hacked','exploit','bullish','bearish',\n 'Grass token','$Grass',\n ];\n\n for (const group of chunk(cryptoTerms, MAX_PER_BATCH_CRYPTO)) {\n const q = `${toOR(group)} ${filters}`;\n batches.push({ rssUrl: toUrl(q), queryReadable: q });\n }\n}\n\n// annotate with batch indexes\nbatches = batches.map((b, i) => ({\n ...b,\n batchIndex: i,\n batchCount: batches.length,\n topic,\n}));\n\nreturn batches.map((b) => ({ json: b }));\n"
},
"typeVersion": 2
},
{
"id": "055c48be-7df5-4338-b847-57af53707580",
"name": "Code - Tag topic - Google news",
"type": "n8n-nodes-base.code",
"position": [
128,
-80
],
"parameters": {
"jsCode": "// Code - Tag topic (place right after RSS Read)\nconst topic = String(\n $json.topic // if present\n ?? $item(0).$node[\"Init RunConfig\"].json.topic // from the config node\n ?? 'crypto'\n).toLowerCase();\n\nreturn $input.all().map(i => ({ json: { ...i.json, topic } }));\n"
},
"typeVersion": 2
},
{
"id": "0ef11d59-ad03-4194-9d51-bb94212ccd2f",
"name": "Code - Tag topic - Coin desk",
"type": "n8n-nodes-base.code",
"position": [
128,
-256
],
"parameters": {
"jsCode": "// Code - Tag topic (place right after RSS Read)\nconst topic = String(\n $json.topic // if present\n ?? $item(0).$node[\"Init RunConfig\"].json.topic // from the config node\n ?? 'crypto'\n).toLowerCase();\n\nreturn $input.all().map(i => ({ json: { ...i.json, topic } }));\n"
},
"typeVersion": 2
},
{
"id": "7c3880d2-e46d-47ff-bf60-fd56496aa3e6",
"name": "Code - Tag topic - X",
"type": "n8n-nodes-base.code",
"position": [
128,
64
],
"parameters": {
"jsCode": "// Code - Tag topic (place right after RSS Read)\nconst topic = String(\n $json.topic // if present\n ?? $item(0).$node[\"Init RunConfig\"].json.topic // from the config node\n ?? 'crypto'\n).toLowerCase();\n\nreturn $input.all().map(i => ({ json: { ...i.json, topic } }));\n"
},
"typeVersion": 2
},
{
"id": "f67bece3-b9a2-4337-b5dd-41b922822d4d",
"name": "Code - URL build - Google news",
"type": "n8n-nodes-base.code",
"position": [
-640,
-80
],
"parameters": {
"jsCode": "const q = $json.queries?.google ?? '(bitcoin OR crypto)'\nconst url = `https://news.google.com/rss/search?q=${encodeURIComponent(q)}&hl=en-US&gl=US&ceid=US:en`\nreturn [{ json: { ...$json, url } }]\n"
},
"typeVersion": 2
},
{
"id": "628c02c2-de72-48a9-8510-8f4a2efee6c8",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1776,
720
],
"parameters": {
"color": 6,
"width": 848,
"height": 928,
"content": "## README\n\nREADME \u2013 Crypto/Stocks News \u2192 UI workflow\n\n### What this does\n- Collects crypto or stock-market news from CoinDesk, CoinTelegraph, Google News, and X (via xcancel.com RSS).\n- Tags each item with topic (crypto or stocks).\n- Filters & deduplicates items in \u201cCode - Keywords Filter\u201d.\n- Bundles everything in \u201cCode - Array bind\u201d as:\n```\n{ topic, items: [ { id, source, kind, title, url, publishedAt, matchedKeywords, summary, html, media[], topic } ] }\n```\n- Sends the items to your backend via \u201cHTTP Request - Send to localhost\u201d.\n\n### How to use\n- Use either \u201cSchedule Trigger\u201d (interval runs) or \u201cWebhook\u201d (run-workflow) as entry.\n- Open \u201cInit RunConfig\u201d to set default topic (crypto | stocks), platforms (coindesk / google / cointelegraph / x) and optional tickers.\n- Update \u201cHTTP Request - Send to localhost\u201d:\n- Change the URL from http://localhost:3000/api/hooks/news to your own API endpoint.\n- Either set a real x-webhook-secret header (and verify it in your backend) or remove that header completely.\n\nIf you call the Webhook node, also fix header auth: create an HTTP Header Auth credential for x-webhook-secret, or switch the node\u2019s auth to \u201cNone\u201d for local tests.\n\n- To change what gets through, edit the keyword & spam lists inside \u201cCode - Keywords Filter\u201d.\n\n### Original purpose\n\nThis workflow was built to transfer curated news data into a custom UI.\n\nYou can instead connect anything after \u201cCode - Array bind\u201d (DB, Slack/Telegram, email, etc.) and reuse the { topic, items } payload for your own use case."
},
"typeVersion": 1
},
{
"id": "3757d187-20b8-4d1b-b05e-89275643edc1",
"name": "HTTP Request - Send to your backend",
"type": "n8n-nodes-base.httpRequest",
"position": [
2288,
64
],
"parameters": {
"url": "https://your-backend.example.com/api/hooks/news",
"method": "POST",
"options": {},
"jsonBody": "={{ { items: $json.items } }}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Content-Type",
"value": "application/json"
},
{
"name": "x-webhook-secret",
"value": "Your Secret Here"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "bedf7a10-89f0-4189-a814-9f84b82fd27a",
"name": "Loop back \u2013 X Batches",
"type": "n8n-nodes-base.noOp",
"position": [
-32,
208
],
"parameters": {},
"typeVersion": 1
},
{
"id": "0824dd31-e98f-4979-9139-208b8cfff5a4",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-880,
720
],
"parameters": {
"width": 416,
"height": 256,
"content": "## CONFIG\n\nEdit these places only for 90% of use cases:\n```\n1. Init RunConfig\n2.URL build nodes \n3. Keywords Filter lists\n4. HTTP Request URL/headers.\n``` "
},
"typeVersion": 1
},
{
"id": "97602b31-2d25-488b-b813-2ee4e0f2a0fe",
"name": "Code - Tag topic - Coin telegraph",
"type": "n8n-nodes-base.code",
"position": [
128,
432
],
"parameters": {
"jsCode": "// Code - Tag topic (place right after RSS Read)\nconst topic = String(\n $json.topic // if present\n ?? $item(0).$node[\"Init RunConfig\"].json.topic // from the config node\n ?? 'crypto'\n).toLowerCase();\n\nreturn $input.all().map(i => ({ json: { ...i.json, topic } }));\n"
},
"typeVersion": 2
},
{
"id": "03e932ee-bb95-48ca-b92f-ce4837fcce42",
"name": "RSS Read - CoinTelegraph",
"type": "n8n-nodes-base.rssFeedRead",
"position": [
-400,
432
],
"parameters": {
"url": "https://cointelegraph.com/rss",
"options": {}
},
"retryOnFail": true,
"typeVersion": 1.2
},
{
"id": "9fe664e3-9624-4c15-a667-b8acacdd5f2e",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1776,
-128
],
"parameters": {
"color": 7,
"width": 640,
"height": 496,
"content": "## Section 1 \u2013 Triggers & Run Config\nThis section controls how the workflow starts and what it should fetch."
},
"typeVersion": 1
},
{
"id": "d42831f4-64bd-4f56-a447-a8c0949f0831",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
-1056,
-336
],
"parameters": {
"color": 7,
"width": 1936,
"height": 1008,
"content": "## Section 2 \u2013 Fetch & Tag News from Sources\nThis section decides which sources to use and pulls news from each of them."
},
"typeVersion": 1
},
{
"id": "8993380b-a412-467c-8b68-b3f930e3891c",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
928,
-336
],
"parameters": {
"color": 7,
"width": 784,
"height": 1008,
"content": "## Section 3 - Merge and normalize All Items\nThis section normalizes metadata and merges all sources into one unified stream."
},
"typeVersion": 1
},
{
"id": "17142132-b6c7-4e08-a17f-3739e1eb355e",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
1744,
-64
],
"parameters": {
"color": 7,
"width": 784,
"height": 400,
"content": "## Section 4 \u2013 Filter, Deduplicate & Build Payload \u2192 Send to Backend\nThis section prepares the final dataset and sends it to your app or UI."
},
"typeVersion": 1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "12b49360-c555-4ae4-be0c-5305dfa19ee3",
"connections": {
"Merge": {
"main": [
[
{
"node": "Init RunConfig",
"type": "main",
"index": 0
}
]
]
},
"Webhook": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 1
}
]
]
},
"X Batches": {
"main": [
[
{
"node": "RSS Read - X Posts",
"type": "main",
"index": 0
}
],
[
{
"node": "Loop back \u2013 X Batches",
"type": "main",
"index": 0
}
]
]
},
"IF Gate - X": {
"main": [
[
{
"node": "Code - URL Build - XCancel",
"type": "main",
"index": 0
}
]
]
},
"Init RunConfig": {
"main": [
[
{
"node": "IF Gate - Coindesk",
"type": "main",
"index": 0
},
{
"node": "IF Gate - Google news",
"type": "main",
"index": 0
},
{
"node": "IF Gate - CoinTelegraph",
"type": "main",
"index": 0
},
{
"node": "IF Gate - X",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger": {
"main": [
[
{
"node": "Merge",
"type": "main",
"index": 0
}
]
]
},
"Code - Array bind": {
"main": [
[
{
"node": "HTTP Request - Send to your backend",
"type": "main",
"index": 0
}
]
]
},
"IF Gate - Coindesk": {
"main": [
[
{
"node": "RSS Read - Coindesk",
"type": "main",
"index": 0
}
]
]
},
"RSS Read - X Posts": {
"main": [
[
{
"node": "Code - Tag topic - X",
"type": "main",
"index": 0
}
]
]
},
"RSS Read - Coindesk": {
"main": [
[
{
"node": "Code - Tag topic - Coin desk",
"type": "main",
"index": 0
}
]
]
},
"Code - Tag topic - X": {
"main": [
[
{
"node": "Code - Accumulate X items",
"type": "main",
"index": 0
}
]
]
},
"IF - More X batches?": {
"main": [
[
{
"node": "X Batches",
"type": "main",
"index": 0
}
],
[
{
"node": "Code - Finalize X batches (emit combined)",
"type": "main",
"index": 0
}
]
]
},
"Source set - X Posts": {
"main": [
[
{
"node": "Merge - X posts + CoinTelegraph",
"type": "main",
"index": 0
}
]
]
},
"IF Gate - Google news": {
"main": [
[
{
"node": "Code - URL build - Google news",
"type": "main",
"index": 0
}
]
]
},
"Source set - Coindesk": {
"main": [
[
{
"node": "Merge - Coindesk + Google news",
"type": "main",
"index": 0
}
]
]
},
"Code - Keywords Filter": {
"main": [
[
{
"node": "Code - Array bind",
"type": "main",
"index": 0
}
]
]
},
"RSS Read - Google news": {
"main": [
[
{
"node": "Code - Tag topic - Google news",
"type": "main",
"index": 0
}
]
]
},
"IF Gate - CoinTelegraph": {
"main": [
[
{
"node": "RSS Read - CoinTelegraph",
"type": "main",
"index": 0
}
]
]
},
"Loop back \u2013 X Batches": {
"main": [
[
{
"node": "X Batches",
"type": "main",
"index": 0
}
]
]
},
"RSS Read - CoinTelegraph": {
"main": [
[
{
"node": "Code - Tag topic - Coin telegraph",
"type": "main",
"index": 0
}
]
]
},
"Source set - Google news": {
"main": [
[
{
"node": "Merge - Coindesk + Google news",
"type": "main",
"index": 1
}
]
]
},
"Code - Accumulate X items": {
"main": [
[
{
"node": "IF - More X batches?",
"type": "main",
"index": 0
}
]
]
},
"Code - Reset X accumulator": {
"main": [
[
{
"node": "X Batches",
"type": "main",
"index": 0
}
]
]
},
"Code - URL Build - XCancel": {
"main": [
[
{
"node": "Code - Reset X accumulator",
"type": "main",
"index": 0
}
]
]
},
"Source set - Cointelegraph": {
"main": [
[
{
"node": "Merge - X posts + CoinTelegraph",
"type": "main",
"index": 1
}
]
]
},
"Code - Tag topic - Coin desk": {
"main": [
[
{
"node": "Source set - Coindesk",
"type": "main",
"index": 0
}
]
]
},
"Code - Tag topic - Google news": {
"main": [
[
{
"node": "Source set - Google news",
"type": "main",
"index": 0
}
]
]
},
"Code - URL build - Google news": {
"main": [
[
{
"node": "RSS Read - Google news",
"type": "main",
"index": 0
}
]
]
},
"Merge - Coindesk + Google news": {
"main": [
[
{
"node": "Merge - Merge previous two merges",
"type": "main",
"index": 0
}
]
]
},
"Merge - X posts + CoinTelegraph": {
"main": [
[
{
"node": "Merge - Merge previous two merges",
"type": "main",
"index": 1
}
]
]
},
"Code - Tag topic - Coin telegraph": {
"main": [
[
{
"node": "Source set - Cointelegraph",
"type": "main",
"index": 0
}
]
]
},
"Merge - Merge previous two merges": {
"main": [
[
{
"node": "Code - Keywords Filter",
"type": "main",
"index": 0
}
]
]
},
"Code - Finalize X batches (emit combined)": {
"main": [
[
{
"node": "Source set - X Posts",
"type": "main",
"index": 0
}
]
]
}
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Collects crypto and/or stock market headlines from multiple sources: CoinDesk, CoinTelegraph, Google News, and X (via an RSS proxy). Normalizes all items into a consistent structure with fields like , , , , , , , and . Uses topic-specific keyword lists to keep relevant items and…
Source: https://n8n.io/workflows/11103/ — 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 workflow pulls news articles from NewsAPI, Mediastack, and CurrentsAPI on a scheduled basis.
I prepared a detailed guide that showed the whole process of integrating the Binance API and storing data in Airtable to manage funding statements associated with tokens in a wallet.
Stop wasting hours on manual dialing and listening to ringtones. This workflow transforms your Airtable into a high-velocity AI Call Center using Vapi AI**.
This template is designed for social media managers, content creators, data analysts, and anyone who wants to automatically save and analyze their Meta Threads posts in Notion.
Reel-Analysis-Of-Favourite-Content-Creator. Uses httpRequest, airtable. Scheduled trigger; 26 nodes.