AutomationFlowsSlack & Telegram › Monitor Google Flights Prices with Apify and Send Slack Alerts

Monitor Google Flights Prices with Apify and Send Slack Alerts

ByRedowan Ahmed Farhan @redowanfarhan on n8n.io

How it works Checks Google Flights prices for different departure dates for given departure and destination place using the Apify scraper Runs every 10 minutes at night and once per hour during the day via a smart schedule gate Aggregates all date results, finds the cheapest…

Event trigger★★★★☆ complexity17 nodesSlack@Apify/N8N Nodes Apify
Slack & Telegram Trigger: Event Nodes: 17 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #15800 — we link there as the canonical source.

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": "7yP3M8dP7Oezu0ST",
  "name": "flights automation",
  "tags": [],
  "nodes": [
    {
      "id": "ce76759a-0a7b-4692-9c36-0ae8a4836f43",
      "name": "When clicking 'Execute workflow'",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        432,
        400
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "9072e06e-0b00-4aad-ba03-2c10225ce562",
      "name": "all dates full report",
      "type": "n8n-nodes-base.slack",
      "position": [
        2544,
        720
      ],
      "parameters": {
        "text": "={{ \n'\ud83d\udcca *FULL 18-DATE PRICE REPORT \u2014 PHL \u2708\ufe0f DAC* | Round Trip | Economy\\n'\n+ '\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\u2501\u2501\\n\\n'\n+ '\ud83d\udcc5 *COMPLETE PRICE CALENDAR \u2014 Jul 1 to Jul 18*\\n'\n+ '\ud83d\udd25 = below $' + $json.TARGET + ' target   |   \ud83d\udc51 = cheapest date\\n\\n'\n+ $json.calendarBlock + '\\n\\n'\n+ '\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\u2501\u2501\\n'\n+ '\ud83d\udcb0 *OVERALL CHEAPEST FOUND THIS RUN*\\n'\n+ '\ud83d\udcb5 Price: *$' + $json.currentPrice + ' USD*\\n'\n+ '\ud83d\udcc5 Date:  *' + $json.cheapestDateDisplay + '*\\n'\n+ ($json.lastPrice !== null ? '\ud83d\udcc9 Previous best: $' + $json.lastPrice + ' USD\\n' : '')\n+ $json.trendEmoji + ' Price *' + $json.trendText + '*\\n'\n+ '\ud83d\udcca Last 5 runs: ' + $json.historyLine + '\\n\\n'\n+ '\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\u2501\u2501\\n'\n+ '\u2708\ufe0f *CHEAPEST FLIGHT DETAILS (departing ' + $json.cheapestDateDisplay + ')*\\n'\n+ '\ud83c\udff7\ufe0f  Airline:    *' + $json.airline + '*\\n'\n+ '\ud83d\udd22  Flight No:  *' + $json.flightNumbers + '*\\n'\n+ '\ud83d\udccd  From:       *Philadelphia Intl Airport (PHL)*\\n'\n+ '\ud83d\udccd  To:         *Hazrat Shahjalal Intl Airport (DAC)*\\n'\n+ '\ud83d\udeeb  Departure:  *' + $json.depTime + '*\\n'\n+ '\ud83d\udeec  Arrival:    *' + $json.arrTime + '*\\n'\n+ '\u23f1\ufe0f  Duration:   *' + $json.duration + '*\\n'\n+ '\ud83d\udd01  Stops:      *' + $json.stopsLabel + '*\\n'\n+ ($json.layovers ? '\ud83c\udfd9\ufe0f  Layovers:   *' + $json.layovers + '*\\n' : '')\n+ '\ud83d\udcb5  Price:      *$' + $json.currentPrice + ' USD*  |  Economy | Round Trip\\n\\n'\n+ '\ud83c\udfab *<' + $json.bookingLink + '|\ud83d\udc49 BOOK ON GOOGLE FLIGHTS>*\\n\\n'\n+ '\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\u2501\u2501\\n'\n+ '\u23f0 Checked: *' + $json.checkedAt + ' ET*\\n'\n+ '\ud83d\udce1 ' + $json.periodLabel + '\\n'\n+ '\ud83d\uddd3\ufe0f Dates scanned: ' + $json.datesChecked + ' | With data: ' + $json.datesWithData\n}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0ASJMR4MMJ",
          "cachedResultName": "sales-team"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "e2171594-c99b-467a-93d8-8324a7f17644",
      "name": "Smart Schedule Gate",
      "type": "n8n-nodes-base.code",
      "position": [
        704,
        400
      ],
      "parameters": {
        "jsCode": "const now       = DateTime.now().setZone('America/New_York');\nconst isNight   = now.hour >= 22 || now.hour < 7;\nconst onTheHour = now.minute === 0;\nconst shouldRun = isNight || (!isNight && onTheHour);\n\nreturn [{\n  json: {\n    shouldRun,\n    periodLabel: isNight\n      ? '\ud83c\udf19 Nighttime Mode \u2014 every 10 min'\n      : '\u2600\ufe0f Daytime Mode \u2014 every 1 hour',\n    checkedAt: now.toFormat('yyyy-MM-dd HH:mm:ss')\n  }\n}];"
      },
      "typeVersion": 2
    },
    {
      "id": "551f2c7a-7a5f-4ce5-8952-5c115eeacc8a",
      "name": "Should Run Now?",
      "type": "n8n-nodes-base.if",
      "position": [
        912,
        400
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-run",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.shouldRun }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "2dcc792a-e586-478e-a82a-d6104f1e7b30",
      "name": "Run an Actor and get dataset",
      "type": "@apify/n8n-nodes-apify.apify",
      "position": [
        1376,
        400
      ],
      "parameters": {
        "memory": 256,
        "actorId": {
          "__rl": true,
          "mode": "list",
          "value": "1dYHRKkEBHBPd0JM7",
          "cachedResultUrl": "https://console.apify.com/actors/1dYHRKkEBHBPd0JM7/input",
          "cachedResultName": "Google Flights API (johnvc/Google-Flights-Data-Scraper-Flight-and-Price-Search)"
        },
        "timeout": {},
        "operation": "Run actor and get dataset",
        "customBody": "={{ JSON.stringify({\n  \"departure_id\": \"PHL\",\n  \"arrival_id\": \"DAC\",\n  \"outbound_date\": $json.departureDate,\n  \"return_date\": DateTime.fromISO($json.departureDate).plus({days: 30}).toFormat('yyyy-MM-dd'),\n  \"adults\": 1,\n  \"currency\": \"USD\",\n  \"hl\": \"en\",\n  \"gl\": \"us\",\n  \"travel_class\": \"1\",\n  \"max_pages\": 1\n}) }}",
        "actorSource": "store",
        "authentication": "apifyOAuth2Api"
      },
      "credentials": {
        "apifyOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "executeOnce": false,
      "typeVersion": 1
    },
    {
      "id": "61818614-9828-4010-8e79-7390fe2bfa37",
      "name": "Below $1,400?",
      "type": "n8n-nodes-base.if",
      "position": [
        2304,
        400
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-below",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.belowTarget }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "b1232ec9-5d31-4de4-881f-e15b7c5206af",
      "name": "Generate dates",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        400
      ],
      "parameters": {
        "jsCode": "// ================================================================\n// GENERATE ALL 18 DATES: July 1 to July 18, 2026\n// Each date becomes a separate item \u2014 Apify node runs once per item\n// ================================================================\nconst sd = $getWorkflowStaticData('global');\nsd.periodLabel = $('Smart Schedule Gate').first().json.periodLabel;\nsd.checkedAt   = $('Smart Schedule Gate').first().json.checkedAt;\n\nconst dates = [];\nlet cur = DateTime.fromISO('2026-07-01', { zone: 'America/New_York' });\nconst end = DateTime.fromISO('2026-07-18', { zone: 'America/New_York' });\n\nwhile (cur <= end) {\n  dates.push({\n    json: {\n      departureDate: cur.toFormat('yyyy-MM-dd'),\n      displayDate:   cur.toFormat('EEE, MMM dd')\n    }\n  });\n  cur = cur.plus({ days: 1 });\n}\n\nreturn dates;"
      },
      "typeVersion": 2
    },
    {
      "id": "6eec680a-f911-4527-8596-10ec73413da6",
      "name": "Extract Cheapest for This Date",
      "type": "n8n-nodes-base.code",
      "position": [
        1632,
        400
      ],
      "parameters": {
        "jsCode": "const item = $input.item.json;\n\nconst depDate  = item?.search_parameters?.outbound_date || 'N/A';\nconst dispDate = depDate !== 'N/A'\n  ? DateTime.fromISO(depDate, { zone: 'America/New_York' }).toFormat('EEE, MMM dd')\n  : 'N/A';\n\nconst lowestPrice = item?.price_insights?.lowest_price || null;\n\nlet allFlights = [];\nif (Array.isArray(item.best_flights))  allFlights = allFlights.concat(item.best_flights);\nif (Array.isArray(item.other_flights)) allFlights = allFlights.concat(item.other_flights);\n\nallFlights = allFlights\n  .filter(f => f && f.price !== null && f.price !== undefined)\n  .sort((a, b) => (a.price || 99999) - (b.price || 99999));\n\nif (!allFlights.length || lowestPrice === null) {\n  return [{ json: {\n    departureDate: depDate,\n    displayDate:   dispDate,\n    available:     false,\n    price:         null,\n    airline:       null,\n    flightNumbers: null,\n    depTime:       null,\n    arrTime:       null,\n    duration:      null,\n    stopsLabel:    null,\n    layovers:      null,\n    bookingLink:   null\n  }}];\n}\n\nconst top   = allFlights[0];\nconst legs  = top.flights || [];\nconst first = legs[0] || {};\nconst last  = legs[legs.length - 1] || {};\nconst stops = legs.length > 0 ? legs.length - 1 : 0;\nconst mins  = top.total_duration || 0;\n\nconst layovers = (top.layovers || []).map(l => {\n  const h = Math.floor((l.duration || 0) / 60);\n  const m = (l.duration || 0) % 60;\n  return `${l.name || '?'} (${h}h ${m}m)`;\n}).join(', ');\n\nconst flightNumbers = legs.map(l => l.flight_number || '').filter(Boolean).join(' \u2192 ') || 'N/A';\nconst airline  = first.airline || top.airline || 'Unknown';\nconst depTime  = (first.departure_airport || {}).time || 'N/A';\nconst arrTime  = (last.arrival_airport || {}).time || 'N/A';\nconst duration = mins > 0 ? `${Math.floor(mins / 60)}h ${mins % 60}m` : 'N/A';\nconst bookingLink = top.booking_link || top.bookingLink ||\n  `https://www.google.com/travel/flights/search?q=flights+from+PHL+to+DAC+on+${depDate}&curr=USD&gl=us&hl=en`;\n\nreturn [{ json: {\n  departureDate: depDate,\n  displayDate:   dispDate,\n  available:     true,\n  price:         lowestPrice,\n  airline,\n  flightNumbers,\n  depTime,\n  arrTime,\n  duration,\n  stopsLabel:    stops === 0 ? 'Nonstop \u2708\ufe0f' : `${stops} Stop${stops > 1 ? 's' : ''}`,\n  layovers,\n  bookingLink\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "ee2d8547-c9d9-4f22-b0c9-88aa6e457787",
      "name": "Collect All 18 Date Results",
      "type": "n8n-nodes-base.aggregate",
      "position": [
        1872,
        400
      ],
      "parameters": {
        "options": {},
        "aggregate": "aggregateAllItemData"
      },
      "typeVersion": 1
    },
    {
      "id": "cf9af521-f96a-46b4-a59c-8a2eec72e0f1",
      "name": "Find Cheapest & Decide",
      "type": "n8n-nodes-base.code",
      "position": [
        2112,
        400
      ],
      "parameters": {
        "jsCode": "// ================================================================\n// MASTER AGGREGATOR\n// Receives all 18 date results, finds cheapest,\n// builds calendar, computes trend, decides Slack channel\n// ================================================================\nconst TARGET = 1400;\nconst sd     = $getWorkflowStaticData('global');\nconst now    = DateTime.now().setZone('America/New_York');\n\nconst checkedAt   = now.toFormat('yyyy-MM-dd HH:mm:ss');\nconst periodLabel = sd.periodLabel || (now.hour >= 22 || now.hour < 7\n  ? '\ud83c\udf19 Nighttime Mode \u2014 every 10 min'\n  : '\u2600\ufe0f Daytime Mode \u2014 every 1 hour');\n\n// Aggregate node wraps all 18 items into one item with a 'data' array\nconst raw      = $input.first().json;\nconst allDates = Array.isArray(raw.data) ? raw.data : [];\n\nconst available = allDates\n  .filter(d => d.available && d.price !== null)\n  .sort((a, b) => a.price - b.price);\n\nif (!available.length) {\n  return [{ json: {\n    hasData:     false,\n    belowTarget: false,\n    checkedAt,\n    periodLabel,\n    calendarBlock: '\u274c No data returned for any date.',\n    error: 'No priced flights found across Jul 1\u201318.'\n  }}];\n}\n\nconst cheapest     = available[0];\nconst currentPrice = cheapest.price;\n\n// Build price calendar\nconst byDate = {};\nfor (const d of allDates) {\n  if (d.available && d.price !== null) byDate[d.departureDate] = d;\n}\n\nconst calLines = [];\nlet cur = DateTime.fromISO('2026-07-01', { zone: 'America/New_York' });\nconst endDt = DateTime.fromISO('2026-07-18', { zone: 'America/New_York' });\nwhile (cur <= endDt) {\n  const key   = cur.toFormat('yyyy-MM-dd');\n  const disp  = cur.toFormat('EEE, MMM dd');\n  const entry = byDate[key];\n  if (entry) {\n    const isBest = key === cheapest.departureDate;\n    const fire   = entry.price < TARGET ? ' \ud83d\udd25' : '';\n    const crown  = isBest ? ' \ud83d\udc51 *CHEAPEST*' : '';\n    calLines.push(`\u2022 *${disp}*: *$${entry.price} USD*${fire}${crown}`);\n  } else {\n    calLines.push(`\u2022 *${disp}*: \u274c No data`);\n  }\n  cur = cur.plus({ days: 1 });\n}\nconst calendarBlock = calLines.join('\\n');\n\n// Price trend\nif (!sd.priceHistory) sd.priceHistory = [];\nconst lastPrice   = sd.lastPrice    || null;\nconst lastAlerted = sd.lastAlertedAt || 0;\n\nlet priceTrend = 'SAME';\nif (lastPrice !== null) {\n  if (Math.round(currentPrice * 100) < Math.round(lastPrice * 100)) priceTrend = 'DOWN';\n  else if (Math.round(currentPrice * 100) > Math.round(lastPrice * 100)) priceTrend = 'UP';\n}\n\nconst priceDiff  = lastPrice !== null ? Math.round((lastPrice - currentPrice) * 100) / 100 : 0;\nconst trendEmoji = priceTrend === 'DOWN' ? '\ud83d\udcc9' : priceTrend === 'UP' ? '\ud83d\udcc8' : '\u27a1\ufe0f';\nconst trendText  = priceTrend === 'DOWN'\n  ? `dropped by $${Math.abs(priceDiff)} since last check`\n  : priceTrend === 'UP'\n    ? `rose by $${Math.abs(priceDiff)} since last check`\n    : 'unchanged since last check';\n\nsd.priceHistory.push({ price: currentPrice, at: checkedAt });\nif (sd.priceHistory.length > 5) sd.priceHistory.shift();\nconst historyLine = sd.priceHistory.map(h => `$${h.price}`).join(' \u2192 ');\n\n// Alert decision\nconst belowTarget = currentPrice < TARGET;\nlet shouldAlert   = false;\nlet alertReason   = '';\n\nif (belowTarget) {\n  if (lastPrice === null) {\n    shouldAlert = true; alertReason = 'FIRST TIME BELOW TARGET';\n  } else if (priceTrend === 'DOWN') {\n    shouldAlert = true; alertReason = 'PRICE DROPPED BELOW TARGET';\n  } else {\n    const hrs = (Date.now() - lastAlerted) / (1000 * 60 * 60);\n    if (hrs >= 2) { shouldAlert = true; alertReason = 'REMINDER \u2014 STILL BELOW TARGET'; }\n  }\n}\n\nsd.lastPrice = currentPrice;\nif (shouldAlert) sd.lastAlertedAt = Date.now();\n\nreturn [{ json: {\n  hasData:              true,\n  belowTarget,\n  shouldAlert,\n  alertReason,\n  currentPrice,\n  lastPrice,\n  priceDiff,\n  priceTrend,\n  trendEmoji,\n  trendText,\n  historyLine,\n  calendarBlock,\n  cheapestDate:         cheapest.departureDate,\n  cheapestDateDisplay:  cheapest.displayDate,\n  airline:              cheapest.airline,\n  flightNumbers:        cheapest.flightNumbers,\n  depTime:              cheapest.depTime,\n  arrTime:              cheapest.arrTime,\n  duration:             cheapest.duration,\n  stopsLabel:           cheapest.stopsLabel,\n  layovers:             cheapest.layovers,\n  bookingLink:          cheapest.bookingLink,\n  TARGET,\n  checkedAt,\n  periodLabel,\n  datesChecked:         allDates.length,\n  datesWithData:        available.length\n}}];"
      },
      "typeVersion": 2
    },
    {
      "id": "2a14a26e-a7d9-4a22-963f-46cf34e33aef",
      "name": "price drop Alert",
      "type": "n8n-nodes-base.slack",
      "position": [
        2512,
        240
      ],
      "parameters": {
        "text": "={{ \n'\ud83d\udea8\ud83d\udea8 *FLIGHT PRICE ALERT \u2014 PHL \u2708\ufe0f DAC* \ud83d\udea8\ud83d\udea8\\n'\n+ '\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\u2501\u2501\\n\\n'\n+ '\ud83d\udcb0 *BEST PRICE FOUND: $' + $json.currentPrice + ' USD*\\n'\n+ '\ud83d\udcc5 *Best Departure Date: ' + $json.cheapestDateDisplay + '*\\n'\n+ ($json.lastPrice !== null ? '\ud83d\udcb5 Previous best: $' + $json.lastPrice + ' USD\\n' : '')\n+ $json.trendEmoji + ' Price has *' + $json.trendText + '*\\n'\n+ '\ud83d\udcca Last 5 runs: ' + $json.historyLine + '\\n'\n+ '\ud83c\udfaf Your target: below $' + $json.TARGET + ' USD\\n\\n'\n+ '\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\u2501\u2501\\n'\n+ '\ud83d\udcc5 *FULL PRICE CALENDAR \u2014 Jul 1 to Jul 18*\\n'\n+ $json.calendarBlock + '\\n\\n'\n+ '\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\u2501\u2501\\n'\n+ '\u2708\ufe0f *CHEAPEST FLIGHT DETAILS*\\n'\n+ '\ud83c\udff7\ufe0f  Airline:    *' + $json.airline + '*\\n'\n+ '\ud83d\udd22  Flight No:  *' + $json.flightNumbers + '*\\n'\n+ '\ud83d\udccd  From:       *Philadelphia Intl Airport (PHL)*\\n'\n+ '\ud83d\udccd  To:         *Hazrat Shahjalal Intl Airport (DAC)*\\n'\n+ '\ud83d\udcc5  Date:       *' + $json.cheapestDateDisplay + '*\\n'\n+ '\ud83d\udeeb  Departure:  *' + $json.depTime + '*\\n'\n+ '\ud83d\udeec  Arrival:    *' + $json.arrTime + '*\\n'\n+ '\u23f1\ufe0f  Duration:   *' + $json.duration + '*\\n'\n+ '\ud83d\udd01  Stops:      *' + $json.stopsLabel + '*\\n'\n+ ($json.layovers ? '\ud83c\udfd9\ufe0f  Layovers:   *' + $json.layovers + '*\\n' : '')\n+ '\ud83d\udcb5  Price:      *$' + $json.currentPrice + ' USD*  |  Economy\\n\\n'\n+ '\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\u2501\u2501\\n'\n+ '\ud83c\udfab *<' + $json.bookingLink + '|\ud83d\udc49 BOOK THIS FLIGHT ON GOOGLE FLIGHTS NOW>*\\n\\n'\n+ '\u23f0 Checked: *' + $json.checkedAt + ' ET*\\n'\n+ '\ud83d\udce1 ' + $json.periodLabel + '\\n'\n+ '\ud83d\udccc Reason: ' + $json.alertReason + '\\n'\n+ '\ud83d\uddd3\ufe0f Dates scanned: ' + $json.datesChecked + ' | With data: ' + $json.datesWithData\n}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0B4D54BBFB",
          "cachedResultName": "price-drop"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "839c6056-78ae-4c1b-b600-997d3cf1a89b",
      "name": "normal update",
      "type": "n8n-nodes-base.slack",
      "position": [
        2528,
        480
      ],
      "parameters": {
        "text": "={{ \n'\ud83d\udd0d *Flight Status \u2014 PHL \u2708\ufe0f DAC* | Google Flights | Economy\\n'\n+ '\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\u2501\u2501\\n\\n'\n+ '\ud83d\udcb5 *Lowest price across Jul 1\u201318: $' + $json.currentPrice + ' USD*\\n'\n+ '\ud83d\udcc5 *Cheapest date: ' + $json.cheapestDateDisplay + '*\\n'\n+ ($json.lastPrice !== null ? '\ud83d\udcb5 Previous best: $' + $json.lastPrice + ' USD\\n' : '')\n+ $json.trendEmoji + ' Price *' + $json.trendText + '*\\n'\n+ '\ud83d\udcca Last 5 runs: ' + $json.historyLine + '\\n'\n+ '\ud83c\udfaf Target: below $' + $json.TARGET + ' \u2014 *not reached yet*\\n\\n'\n+ '\ud83d\udcc5 *PRICE CALENDAR \u2014 Jul 1 to Jul 18*\\n'\n+ $json.calendarBlock + '\\n\\n'\n+ '\u2708\ufe0f *Cheapest Flight Right Now*\\n'\n+ '\ud83c\udff7\ufe0f ' + $json.airline + '   \ud83d\udd22 ' + $json.flightNumbers + '\\n'\n+ '\ud83d\udcc5 ' + $json.cheapestDateDisplay + '\\n'\n+ '\ud83d\udeeb ' + $json.depTime + '  \u2192  \ud83d\udeec ' + $json.arrTime + '\\n'\n+ '\u23f1\ufe0f ' + $json.duration + '   \ud83d\udd01 ' + $json.stopsLabel + '\\n'\n+ ($json.layovers ? '\ud83c\udfd9\ufe0f Layovers: ' + $json.layovers + '\\n' : '')\n+ '\\n'\n+ '\ud83d\udd17 *<' + $json.bookingLink + '|View on Google Flights>*\\n\\n'\n+ '\u23f0 Checked: *' + $json.checkedAt + ' ET*\\n'\n+ '\ud83d\udce1 ' + $json.periodLabel + '\\n'\n+ '\ud83d\uddd3\ufe0f Dates scanned: ' + $json.datesChecked + ' | With data: ' + $json.datesWithData\n}}",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": "C0B4B9XVD98",
          "cachedResultName": "regular-update"
        },
        "otherOptions": {
          "includeLinkToWorkflow": false
        },
        "authentication": "oAuth2"
      },
      "credentials": {
        "slackOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "084ddd16-8915-4a25-bc2a-1b8943b3a77b",
      "name": "Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -304,
        -64
      ],
      "parameters": {
        "color": 7,
        "width": 560,
        "height": 1004,
        "content": "## \u2708\ufe0f Flights Price Monitor \n\nThis workflow automatically tracks economy round-trip flight prices from different destinations across different departure dates , posting price updates and alerts to Slack whenever a deal is found or prices change.\n\n**Perfect for:** Travelers or deal-hunters who want hands-free flight price monitoring with instant Slack notifications.\n\n---\n\n## How it works\n\n1. **When clicking 'Execute workflow'** \u2014 Manually triggers the workflow for testing or on-demand checks.\n2. **Smart Schedule Gate** \u2014 Determines the current time zone and run mode: every 10 minutes at night, or once per hour during the day.\n3. **Should Run Now?** \u2014 Gates execution based on the schedule logic; stops the run if it is not the right time.\n4. **Generate dates** \u2014 Produces separate items, one for each departure date for given date.\n5. **Run an Actor and get dataset** \u2014 Calls the Apify Google Flights scraper once per date to fetch live pricing data from Google Flights for given destination.\n6. **Extract Cheapest for This Date** \u2014 Parses the Apify response, identifies the lowest-priced flight for that date, and extracts airline, flight numbers, times, duration, stops, and booking link.\n7. **Collect All  Date Results** \u2014 Aggregates all  individual date results into a single item for comparison.\n8. **Find Cheapest & Decide** \u2014 Compares all dates to find the overall cheapest, builds the full price calendar, computes the price trend against the last run, and determines whether to fire a price-drop alert.\n9. **Below given price ?** \u2014 Branches on whether the best price found is below the given price target threshold.\n10. **price drop Alert** \u2014 Sends an urgent Slack alert to the #price-drop channel when the price is below target.\n11. **normal update** \u2014 Sends a standard status update to the #regular-update channel when no threshold is breached.\n12. **all dates full report** \u2014 Sends a comprehensive 18-date price calendar report to the #sales-team channel on every run.\n\n---\n\n## Setup (~10 minutes)\n\n1. **Apify OAuth2 API** \u2014 Connect your Apify account and confirm access to the Google Flights scraper actor in the *Run an Actor and get dataset* node.\n2. **Slack OAuth2 API** \u2014 Authenticate your Slack workspace in the *price drop Alert*, *normal update*, and *all dates full report* nodes.\n3. **Slack channel IDs** \u2014 Verify the channel IDs for #price-drop, #regular-update, and #sales-team are correct in each respective Slack node.\n> This workflow uses Apify actor credits on every run across 18 dates. Monitor your Apify usage to avoid unexpected charges."
      },
      "typeVersion": 1
    },
    {
      "id": "bab7f189-0f8a-485a-a88a-bb5cbeda9798",
      "name": "Section 1: Trigger & Schedule",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        288,
        240
      ],
      "parameters": {
        "color": 5,
        "width": 760,
        "height": 380,
        "content": "## 1\ufe0f\u20e3 Trigger & Schedule Gate\n\nThe **When clicking 'Execute workflow'** node starts the process manually or on a schedule. The **Smart Schedule Gate** code node reads the current time in the America/New_York zone and decides whether the workflow is in nighttime mode (every 10 minutes) or daytime mode (once per hour). The **Should Run Now?** If node enforces this gate, stopping execution immediately if the timing conditions are not met."
      },
      "typeVersion": 1
    },
    {
      "id": "22556b5f-6c6e-41cc-979e-f8a674ab6b7e",
      "name": "Section 2: Date Generation & Scraping",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1072,
        240
      ],
      "parameters": {
        "color": 3,
        "width": 660,
        "height": 380,
        "content": "## 2\ufe0f\u20e3 Date Generation & Flight Scraping\n\nThe **Generate dates** code node produces 18 separate output items, one for each departure date from July 1 through July 18, 2026. For each date, **Run an Actor and get dataset** invokes the Apify Google Flights scraper with the PHL to DAC route and a 30-day return window, fetching live price data. The **Extract Cheapest for This Date** code node then parses the raw Apify response to pull out the lowest-priced flight option, its airline, flight numbers, departure and arrival times, duration, stop count, and a direct booking link."
      },
      "typeVersion": 1
    },
    {
      "id": "ae92ed27-d49b-4e4a-a7f0-f45447a77ae8",
      "name": "Section 3: Aggregation & Decision",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1760,
        240
      ],
      "parameters": {
        "color": 6,
        "width": 640,
        "height": 380,
        "content": "## 3\ufe0f\u20e3 Aggregation & Price Decision\n\nThe **Collect All 18 Date Results** aggregate node combines all 18 individual date items into a single payload. The **Find Cheapest & Decide** code node then compares every date to identify the overall cheapest flight, builds a full 18-date price calendar with \ud83d\udd25 and \ud83d\udc51 markers, computes the price trend against the previous run using persistent static data, and sets a flag for whether the price is below the $1,400 target. The **Below $1,400?** If node branches the flow depending on whether that threshold has been crossed."
      },
      "typeVersion": 1
    },
    {
      "id": "b923d914-ab0a-4d37-a238-e8b997619e4a",
      "name": "Section 4: Slack Notifications",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2416,
        16
      ],
      "parameters": {
        "color": 4,
        "width": 440,
        "height": 856,
        "content": "## 4\ufe0f\u20e3 Slack Notifications\n\nThree Slack nodes handle outbound messaging to different audiences. The **price drop Alert** node fires only when the price is below $1,400, sending a high-priority alert with full flight details to the #price-drop channel. The **normal update** node posts a standard status summary to the #regular-update channel when no threshold is breached. The **all dates full report** node sends a comprehensive 18-date price calendar to the #sales-team channel on every successful run, regardless of price level."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "cfd4c219-6407-4823-8c09-eb8cd6d5127e",
  "connections": {
    "Below $1,400?": {
      "main": [
        [
          {
            "node": "price drop Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "normal update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate dates": {
      "main": [
        [
          {
            "node": "Run an Actor and get dataset",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Should Run Now?": {
      "main": [
        [
          {
            "node": "Generate dates",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Smart Schedule Gate": {
      "main": [
        [
          {
            "node": "Should Run Now?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Find Cheapest & Decide": {
      "main": [
        [
          {
            "node": "Below $1,400?",
            "type": "main",
            "index": 0
          },
          {
            "node": "all dates full report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Collect All 18 Date Results": {
      "main": [
        [
          {
            "node": "Find Cheapest & Decide",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Run an Actor and get dataset": {
      "main": [
        [
          {
            "node": "Extract Cheapest for This Date",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Cheapest for This Date": {
      "main": [
        [
          {
            "node": "Collect All 18 Date Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking 'Execute workflow'": {
      "main": [
        [
          {
            "node": "Smart Schedule Gate",
            "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.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

How it works Checks Google Flights prices for different departure dates for given departure and destination place using the Apify scraper Runs every 10 minutes at night and once per hour during the day via a smart schedule gate Aggregates all date results, finds the cheapest…

Source: https://n8n.io/workflows/15800/ — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

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

Slack & Telegram

Webhook Slack. Uses theHiveProjectTrigger, stickyNote, httpRequest, theHiveProject. Event-driven trigger; 63 nodes.

The Hive Project Trigger, HTTP Request, The Hive Project +1
Slack & Telegram

Key Features: Direct Case Management: Modify case details such as assignee, severity, status, and more through intuitive form inputs embedded within Slack messages. Seamless Integration: Assumes match

The Hive Project Trigger, HTTP Request, The Hive Project +1
Slack & Telegram

Transform your lead list into an AI-powered calling machine. This workflow automates your entire cold calling process using Vapi's conversational AI to initiate calls, qualify leads, capture detailed

Google Sheets, HTTP Request, Slack
Slack & Telegram

Type in Slack. Walk away. Get a professional PDF report and a structured Excel fix sheet delivered to Google Drive and posted back in your Slack thread — fully automated, zero manual work.

Compression, HTTP Request, Google Drive +3
Slack & Telegram

This workflow automates the full company enrichment pipeline: Simply import CSV company lists to Slack and save time on enrichment and CRM maintenance. It processes uploaded files, extracts company do

Slack Trigger, Slack, HTTP Request +3