This workflow corresponds to n8n.io template #15173 — we link there as the canonical source.
This workflow follows the HTTP Request → Telegram recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"meta": {
"templateCredsSetupCompleted": true
},
"nodes": [
{
"id": "77d347e1-1f86-48d8-9475-d1c8557fb424",
"name": "Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
-144,
-16
],
"parameters": {
"color": 7,
"width": 400,
"height": 520,
"content": "## Henry Hub Futures Tracker\n\nScrapes real-time Henry Hub data, extracts spot price and the next 12 monthly futures contracts, and delivers a clean tabular update to Telegram.\n\n### How it works\n1. Triggers multiple times during market hours\n2. Scrapes the Henry Hub futures page\n3. Extracts current spot price and 12 forward contracts\n4. Formats data into a structured HTML table\n5. Sends the update to your Telegram channel/chat\n\n### Setup\n- [ ] Add your Telegram Bot credentials\n- [ ] Replace `YOUR_TELEGRAM_CHAT_ID` in the final node\n- [ ] Adjust Schedule Trigger for your specific time zone\n\n### How to Use\nActivate the workflow and receive automated market updates, or press 'Test Workflow' for an immediate pull."
},
"typeVersion": 1
},
{
"id": "679dd1f7-e910-489e-ae4c-339b1a581bbd",
"name": "Sec1",
"type": "n8n-nodes-base.stickyNote",
"position": [
304,
96
],
"parameters": {
"width": 460,
"height": 276,
"content": "## 1. Fetch\nTriggers and pulls HTML."
},
"typeVersion": 1
},
{
"id": "92eee4f6-2997-4185-bf9f-4616c71c0e69",
"name": "Sec3",
"type": "n8n-nodes-base.stickyNote",
"position": [
1264,
96
],
"parameters": {
"width": 282,
"height": 276,
"content": "## 3. Send\nDelivers to Telegram."
},
"typeVersion": 1
},
{
"id": "4c5000ed-1a4f-4d84-b110-8ef575768b8b",
"name": "Fetch Natural Gas Page1",
"type": "n8n-nodes-base.httpRequest",
"position": [
624,
208
],
"parameters": {
"url": "https://oilprice.com/futures/natural-gas/",
"options": {}
},
"typeVersion": 4.3
},
{
"id": "3254ebf7-a419-4a02-b77e-f71d8d24d597",
"name": "Market Hours Trigger1",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
400,
208
],
"parameters": {
"rule": {
"interval": [
{
"field": "weeks",
"triggerAtDay": [
1,
2,
3,
4,
5
],
"triggerAtHour": 10
},
{
"field": "weeks",
"triggerAtDay": [
1,
2,
3,
4,
5
],
"triggerAtHour": 12
},
{
"field": "weeks",
"triggerAtDay": [
1,
2,
3,
4,
5
],
"triggerAtHour": 14
},
{
"field": "weeks",
"triggerAtDay": [
1,
2,
3,
4,
5
],
"triggerAtHour": 16
},
{
"field": "weeks",
"triggerAtDay": [
1,
2,
3,
4,
5
],
"triggerAtHour": 18,
"triggerAtMinute": 19
},
{
"field": "weeks",
"triggerAtDay": [
1,
2,
3,
4,
5
],
"triggerAtHour": 19,
"triggerAtMinute": 20
}
]
}
},
"typeVersion": 1.3
},
{
"id": "b4c864af-128c-4434-bf0c-a4dcd76f5794",
"name": "Extract Spot & Futures1",
"type": "n8n-nodes-base.code",
"position": [
848,
208
],
"parameters": {
"jsCode": "// n8n Code Node to Extract Henry Hub Natural Gas Futures Prices + CURRENT SPOT PRICE\n// This function extracts all available contract data AND the current real-time price\n\nconst items = $input.all();\nconst extractedData = [];\n\nfor (const item of items) {\n const htmlContent = item.json.data;\n \n // ========================================\n // EXTRACT CURRENT SPOT PRICE (Real-time)\n // ========================================\n let currentSpotPrice = null;\n let currentChange = null;\n let currentChangePercent = null;\n let currentChangeDirection = null;\n \n // Look for the current Natural Gas spot price in the header section\n const spotPricePattern = /<tr class=\"[^\"]*\" data-hash=\"Natural-Gas\"[^>]*>(.*?)<\\/tr>/s;\n const spotMatch = htmlContent.match(spotPricePattern);\n \n if (spotMatch) {\n const spotRow = spotMatch[1];\n \n // Extract current price\n const priceMatch = spotRow.match(/<td class=\"value\">\\s*([\\d.]+)\\s*<i/);\n if (priceMatch) {\n currentSpotPrice = parseFloat(priceMatch[1]);\n }\n \n // Extract change value\n const changeMatch = spotRow.match(/<td class=\"change_amount\">\\s*([+\\-][\\d.]+)/);\n if (changeMatch) {\n currentChange = parseFloat(changeMatch[1]);\n }\n \n // Extract change percentage\n const percentMatch = spotRow.match(/<td class=\"change_percent\">\\s*([+\\-][\\d.]+)%/);\n if (percentMatch) {\n currentChangePercent = parseFloat(percentMatch[1]);\n }\n \n // Extract direction (up/down)\n const directionMatch = spotRow.match(/class=\"(change_up|change_down)/);\n if (directionMatch) {\n currentChangeDirection = directionMatch[1].replace('change_', '');\n }\n }\n \n // ========================================\n // EXTRACT FUTURES CONTRACTS DATA\n // ========================================\n const rowPattern = /<div class=\"info_table_row\" data-symbol=\"([^\"]+)\" data-days_5='([^']*)' data-days_30='([^']*)' data-days_90='([^']*)' data-year='([^']*)' data-ytd='([^']*)'[^>]*>((?:(?!<div class=\"info_table_row\").)*?)<\\/div>/gs;\n \n const contracts_list = [];\n let match;\n \n while ((match = rowPattern.exec(htmlContent)) !== null) {\n const symbol = match[1];\n const days5Price = match[2] ? parseFloat(match[2]) : null;\n const days30Price = match[3] ? parseFloat(match[3]) : null;\n const days90Price = match[4] ? parseFloat(match[4]) : null;\n const yearPrice = match[5] ? parseFloat(match[5]) : null;\n const ytdPrice = match[6] ? parseFloat(match[6]) : null;\n const innerHtml = match[7];\n \n // Extract contract details\n const nameMatch = innerHtml.match(/data-name=\"([^\"]+)\"/);\n const lastPriceMatch = innerHtml.match(/<div class=\"info_table_cell last_price\">\\s*([\\d.]+)/);\n const changeMatch = innerHtml.match(/<div class=\"info_table_cell (up|down) change\">\\s*([+\\-][\\d.]+)/);\n const openMatch = innerHtml.match(/<div class=\"info_table_cell open\">\\s*([\\d.]+)/);\n const highMatch = innerHtml.match(/<div class=\"info_table_cell high\">\\s*([\\d.]+)/);\n const lowMatch = innerHtml.match(/<div class=\"info_table_cell low\">\\s*([\\d.]+)/);\n const dateMatch = innerHtml.match(/<div class=\"info_table_cell date\">\\s*([^<\\n]+)/);\n \n const lastPrice = lastPriceMatch ? parseFloat(lastPriceMatch[1]) : null;\n const changeDirection = changeMatch ? changeMatch[1] : null;\n const changeValue = changeMatch ? parseFloat(changeMatch[2]) : null;\n \n const contractData = {\n symbol: symbol,\n contract_name: nameMatch ? nameMatch[1] : '',\n last_price: lastPrice,\n change_direction: changeDirection,\n change_value: changeValue,\n change_percent: lastPrice && changeValue ? parseFloat(((changeValue / lastPrice) * 100).toFixed(2)) : null,\n open: openMatch ? parseFloat(openMatch[1]) : null,\n high: highMatch ? parseFloat(highMatch[1]) : null,\n low: lowMatch ? parseFloat(lowMatch[1]) : null,\n date_time: dateMatch ? dateMatch[1].trim() : '',\n price_5_days_ago: days5Price,\n price_30_days_ago: days30Price,\n price_90_days_ago: days90Price,\n price_1_year_ago: yearPrice,\n price_ytd: ytdPrice\n };\n \n // Calculate percentage changes\n if (lastPrice) {\n if (days5Price) contractData.change_5_days_percent = parseFloat((((lastPrice - days5Price) / days5Price) * 100).toFixed(2));\n if (days30Price) contractData.change_30_days_percent = parseFloat((((lastPrice - days30Price) / days30Price) * 100).toFixed(2));\n if (days90Price) contractData.change_90_days_percent = parseFloat((((lastPrice - days90Price) / days90Price) * 100).toFixed(2));\n if (yearPrice) contractData.change_1_year_percent = parseFloat((((lastPrice - yearPrice) / yearPrice) * 100).toFixed(2));\n if (ytdPrice) contractData.change_ytd_percent = parseFloat((((lastPrice - ytdPrice) / ytdPrice) * 100).toFixed(2));\n }\n \n contractData.scraped_at = new Date().toISOString();\n contracts_list.push(contractData);\n }\n \n // ========================================\n // COMBINE SPOT PRICE + CONTRACTS\n // ========================================\n // Add spot price data as a special entry at the beginning\n if (currentSpotPrice) {\n extractedData.push({\n type: 'spot_price',\n spot_price: currentSpotPrice,\n spot_change: currentChange,\n spot_change_percent: currentChangePercent,\n spot_direction: currentChangeDirection,\n timestamp: new Date().toISOString(),\n timestamp_ist: new Date().toLocaleString('en-IN', { \n timeZone: 'Asia/Kolkata',\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n hour12: true\n })\n });\n }\n \n // Add all contract data\n contracts_list.forEach(contract => {\n extractedData.push({\n type: 'futures_contract',\n ...contract\n });\n });\n}\n\nreturn extractedData.map(data => ({ json: data }));\n"
},
"typeVersion": 2
},
{
"id": "9f9ea823-ff65-4038-b2f2-210c56dbe458",
"name": "Build HTML Table1",
"type": "n8n-nodes-base.code",
"position": [
1072,
208
],
"parameters": {
"jsCode": "// Get all input data\nconst items = $input.all();\n\n// Separate spot price and futures contracts\nlet spotData = null;\nconst futuresContracts = [];\n\nitems.forEach(item => {\n if (item.json.type === 'spot_price') {\n spotData = item.json;\n } else if (item.json.type === 'futures_contract') {\n futuresContracts.push(item.json);\n }\n});\n\n// Build the message\nlet message = `\ud83d\udcca <b>HENRY HUB NATURAL GAS FUTURES</b>\\n`;\nmessage += `\ud83d\udcc5 ${spotData.timestamp_ist}\\n`;\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n\\n`;\n\n// Spot Price Section - TABLE FORMAT (same as futures)\nconst isPositive = spotData.spot_change > 0;\nconst indicator = isPositive ? '\ud83d\udfe2' : '\ud83d\udd34';\n\nmessage += `\ud83d\udcb0 <b>SPOT PRICE</b>\\n\\n`;\nmessage += `<pre>`;\nmessage += `Contract Period Price Change\\n`;\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\n\n// Format spot price row\nconst spotSymbol = `${indicator} SPOT `.padEnd(10);\nconst today = new Date();\nconst todayStr = today.toLocaleDateString('en-GB', { day: 'numeric', month: 'short', year: '2-digit' }).replace(/ /g, ' ');\nconst spotPeriod = `Today`.padEnd(10);\nconst spotPrice = `$${spotData.spot_price.toFixed(2)}`.padStart(7);\nconst spotChange = `${isPositive ? '$+' : '$'}${Math.abs(spotData.spot_change).toFixed(2)}`.padStart(8);\n\nmessage += `${spotSymbol} ${spotPeriod} ${spotPrice} ${spotChange}\\n`;\nmessage += `</pre>\\n\\n`;\n\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\nmessage += `\ud83d\udcc8 <b>NEXT 12 MONTHS</b>\\n\\n`;\n\n// Futures contracts table\nmessage += `<pre>`;\nmessage += `Contract Period Price Change\\n`;\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\n\nconst monthlyContracts = futuresContracts.slice(0, 12);\n\nmonthlyContracts.forEach(contract => {\n // Extract month and year from contract name\n const nameParts = contract.contract_name.replace('Natural Gas ', '').split(' ');\n const month = nameParts[0].substring(0, 3); // First 3 letters of month\n const year = `'${nameParts[1].substring(2)}`; // Last 2 digits of year\n \n // Use 30-day price as current (since last_price is null)\n const currentPrice = contract.price_30_days_ago;\n \n // Calculate change from 1 year ago\n const changeValue = contract.price_1_year_ago ? currentPrice - contract.price_1_year_ago : 0;\n \n // Format the row with proper spacing\n const symbol = `\ud83d\udd34 ${contract.symbol.padEnd(6)}`;\n const period = `${month} ${year}`.padEnd(10);\n const price = `$${currentPrice.toFixed(2)}`.padStart(7);\n const change = `${changeValue > 0 ? '$+' : '$'}${changeValue.toFixed(2)}`.padStart(8);\n \n message += `${symbol} ${period} ${price} ${change}\\n`;\n});\n\nmessage += `</pre>\\n\\n`;\n\n// Summary stats\nconst monthlyPrices = monthlyContracts.map(c => c.price_30_days_ago);\nconst lowestPrice = Math.min(...monthlyPrices);\nconst highestPrice = Math.max(...monthlyPrices);\nconst avgPrice = monthlyPrices.reduce((a, b) => a + b, 0) / monthlyPrices.length;\n\nmessage += `\ud83d\udcca Range: $${lowestPrice.toFixed(2)} - $${highestPrice.toFixed(2)}\\n`;\nmessage += `\ud83d\udcc8 Avg: $${avgPrice.toFixed(2)}\\n\\n`;\n\nmessage += `\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\\n`;\nmessage += `<i>\ud83d\udd14 Source: Henry Hub Futures Data</i>`;\n\n// Return the formatted message\nreturn {\n json: {\n telegram_message: message\n }\n};"
},
"typeVersion": 2
},
{
"id": "3341bc66-95c5-4b39-99f5-d570f57ed9ae",
"name": "Send Telegram Update1",
"type": "n8n-nodes-base.telegram",
"position": [
1312,
208
],
"parameters": {
"text": "={{ $json.telegram_message }}",
"chatId": "YOUR_TELEGRAM_CHAT_ID",
"additionalFields": {
"parse_mode": "HTML",
"appendAttribution": false
}
},
"credentials": {
"telegramApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.2
},
{
"id": "c66e004b-ef3c-49c4-8e64-7888ff5a89f1",
"name": "Sec2",
"type": "n8n-nodes-base.stickyNote",
"position": [
784,
96
],
"parameters": {
"width": 436,
"height": 276,
"content": "## 2. Process\nParses prices and formats table."
},
"typeVersion": 1
}
],
"connections": {
"Build HTML Table1": {
"main": [
[
{
"node": "Send Telegram Update1",
"type": "main",
"index": 0
}
]
]
},
"Market Hours Trigger1": {
"main": [
[
{
"node": "Fetch Natural Gas Page1",
"type": "main",
"index": 0
}
]
]
},
"Extract Spot & Futures1": {
"main": [
[
{
"node": "Build HTML Table1",
"type": "main",
"index": 0
}
]
]
},
"Fetch Natural Gas Page1": {
"main": [
[
{
"node": "Extract Spot & Futures1",
"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.
telegramApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
Natural gas traders, energy analysts, LNG desk professionals, utility planners, industrial gas buyers, power generation schedulers, pipeline operations teams, commodity research desks, and macro researchers tracking the NYMEX Henry Hub benchmark. If you start your trading day…
Source: https://n8n.io/workflows/15173/ — 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.
GNCA AI News Pipeline. Uses rssFeedRead, httpRequest, telegram, errorTrigger. Scheduled trigger; 29 nodes.
This workflow automates plant care reminders and records using Google Sheets, Telegram, and OpenWeather API.
Apollo Data Enrichment Using Company Id to automatically finds contacts for companies listed in your Google Sheet, enriches each person with emails and phone numbers via Apollo’s API, and writes verif
MindFrame Psychology - FREE Complete Workflow. Uses httpRequest, googleDrive, telegram. Scheduled trigger; 25 nodes.
++Download the google sheet here++ and replace this with the googles sheet node: Google sheet , upload to google sheets and replace in the google sheets node. Scheduled trigger: Runs once a day at 8 A