AutomationFlowsData & Sheets › Aggregate Crypto and Stock Market News Feed From Multiple Sources

Aggregate Crypto and Stock Market News Feed From Multiple Sources

ByMohammad Abubakar @m7abr on n8n.io

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…

Cron / scheduled trigger★★★★★ complexity40 nodesRSS Feed ReadHTTP Request
Data & Sheets Trigger: Cron / scheduled Nodes: 40 Complexity: ★★★★★ Added:

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 →

Download .json
{
  "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
          }
        ]
      ]
    }
  }
}
Pro

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 →

More Data & Sheets workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Data & Sheets

This workflow pulls news articles from NewsAPI, Mediastack, and CurrentsAPI on a scheduled basis.

Noco Db, HTTP Request
Data & Sheets

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.

Crypto, HTTP Request, Airtable
Data & Sheets

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**.

Airtable, HTTP Request
Data & Sheets

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.

HTTP Request, Notion
Data & Sheets

Reel-Analysis-Of-Favourite-Content-Creator. Uses httpRequest, airtable. Scheduled trigger; 26 nodes.

HTTP Request, Airtable