{
  "name": "Uptime monitor that pings you on WhatsApp when a site goes down (and when it's back)",
  "nodes": [
    {
      "parameters": {
        "content": "## Uptime monitor that pings you on WhatsApp when a site goes down (and when it's back)\n\nWhat you get: a check of your websites every 5 minutes and a WhatsApp alert only when a site changes state -- down, then back up. No repeated alerts.\nWhat it replaces: paid uptime services for a handful of sites, and finding out from a customer that the shop was offline.\nSetup time: about 5 minutes.\n\nTwo messages per incident -- \"down\" and \"back\" -- on the channel you actually look at.\n\nWorks with a normal WhatsApp number -- no Meta Business account needed.\n\nHow it works: the \"Track State Changes\" Code node remembers each site's last known status in this workflow's static data ($getWorkflowStaticData('global')), which n8n persists on the workflow itself between scheduled runs. The first run after you activate the workflow only records a baseline (no alert, since there's nothing to compare against yet) -- alerts start from the second run onward. If you deactivate and reactivate the workflow, or move it to a different n8n instance, this memory resets and you'll get one \"state\" message on the next run for whatever is or isn't up at that point.\n\nSetup:\n1. Install the community node: n8n Settings -> Community Nodes -> npm package `n8n-nodes-wasync`.\n2. Create a free WASync account at https://developers.wasync.app/register, connect a WhatsApp number, then create an API key at https://developers.wasync.app/keys.\n3. In n8n, add a new \"WASync API\" credential using that key.\n4. Open the \"Settings\" node at the top of this workflow and fill in your own values (the list of sites and the phone number to alert). Never commit real portal URLs, tokens, or phone numbers.\n\nDocs: https://docs.wasync.app/docs/guides/n8n?utm_source=n8n&utm_medium=template&utm_campaign=t7",
        "height": 620,
        "width": 1880
      },
      "name": "Read Me First",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -140,
        -700
      ]
    },
    {
      "parameters": {
        "content": "**① Trigger**\nRuns every 5 minutes, around the clock.",
        "height": 344,
        "width": 280,
        "color": 7
      },
      "name": "① Trigger",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -140,
        404
      ]
    },
    {
      "parameters": {
        "content": "**② Check every site**\nOne HTTP check per site, then a Code node that remembers the\nlast known state and only emits when a site changes it.",
        "height": 344,
        "width": 1240,
        "color": 7
      },
      "name": "② Check every site",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        180,
        404
      ]
    },
    {
      "parameters": {
        "content": "**③ Send on WhatsApp**\nTwo messages per incident: \"down\", then \"back up\".\nNo repeated alerts while a site stays down.",
        "height": 344,
        "width": 280,
        "color": 7
      },
      "name": "③ Send on WhatsApp",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        1460,
        404
      ]
    },
    {
      "parameters": {
        "content": "**Settings — fill these in**\nPlaceholders only — replace every value here before you activate the\nworkflow. Nothing else in the workflow needs editing.\nFields: sites, alertTo, wasyncConnectionId.",
        "height": 344,
        "width": 600,
        "color": 7
      },
      "name": "Settings — fill these in",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -140,
        0
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      },
      "name": "Run Every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.4,
      "position": [
        0,
        628
      ]
    },
    {
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "Settings-0",
              "name": "sites",
              "value": "={{ ['https://example.com', 'https://shop.example.com'] }}",
              "type": "array"
            },
            {
              "id": "Settings-1",
              "name": "alertTo",
              "value": "REPLACE_WITH_YOUR_WHATSAPP_NUMBER",
              "type": "string"
            },
            {
              "id": "Settings-2",
              "name": "wasyncConnectionId",
              "value": "REPLACE_WITH_YOUR_WASYNC_CONNECTION_ID",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        0,
        224
      ]
    },
    {
      "parameters": {
        "fieldToSplitOut": "sites",
        "options": {
          "destinationFieldName": "checkedUrl"
        }
      },
      "name": "Split Sites",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        320,
        628
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $json.checkedUrl }}",
        "options": {
          "timeout": 10000,
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        }
      },
      "name": "Check Site Is Up",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        628
      ],
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "Norm-0",
              "name": "checkedUrl",
              "value": "={{ $('Split Sites').item.json.checkedUrl }}",
              "type": "string"
            },
            {
              "id": "Norm-1",
              "name": "statusCode",
              "value": "={{ $json.statusCode }}",
              "type": "number"
            },
            {
              "id": "Norm-2",
              "name": "wasyncCheckFailed",
              "value": "={{ $json.error ? true : false }}",
              "type": "boolean"
            }
          ]
        },
        "options": {}
      },
      "name": "Normalize Check Result",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        960,
        628
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "// Keep the last known up/down state per URL in this workflow's static data, and only emit an item\n// when a site's state actually changes -- so we alert once per incident, not every 5 minutes.\nconst staticData = $getWorkflowStaticData('global');\nif (!staticData.siteState) staticData.siteState = {};\n\nconst now = Date.now();\nconst changes = [];\n\nfor (const item of $input.all()) {\n  const url = item.json.checkedUrl;\n  if (!url) continue;\n\n  const statusCode = item.json.statusCode;\n  const requestFailed = item.json.wasyncCheckFailed === true;\n  const nowUp = !requestFailed && typeof statusCode === 'number' && statusCode >= 200 && statusCode < 400;\n\n  const prev = staticData.siteState[url];\n\n  if (!prev) {\n    // First time we've seen this site: just record its state, nothing to compare against yet.\n    staticData.siteState[url] = { up: nowUp, since: now };\n    continue;\n  }\n\n  if (prev.up !== nowUp) {\n    let text;\n    if (!nowUp) {\n      text = `\\ud83d\\udd34 DOWN: ${url} since ${new Date(now).toISOString()}`;\n    } else {\n      const minutesDown = Math.round((now - prev.since) / 60000);\n      text = `\\ud83d\\udfe2 BACK UP: ${url} after ${minutesDown} minute(s)`;\n    }\n    changes.push({ json: { url, text } });\n    staticData.siteState[url] = { up: nowUp, since: now };\n  }\n  // else: no change, stay silent -- this is what stops repeated alerts while a site stays down.\n}\n\nreturn changes;"
      },
      "name": "Track State Changes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1280,
        628
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "connectionId": "={{ $('Settings').item.json.wasyncConnectionId }}",
        "to": "={{ $('Settings').item.json.alertTo }}",
        "text": "={{ $json.text }}",
        "additionalFields": {}
      },
      "name": "Send WhatsApp Alert",
      "type": "n8n-nodes-wasync.wasync",
      "typeVersion": 1,
      "position": [
        1600,
        628
      ],
      "credentials": {
        "wasyncApi": {
          "name": "WASync account"
        }
      }
    }
  ],
  "connections": {
    "Run Every 5 Minutes": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Split Sites",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Split Sites": {
      "main": [
        [
          {
            "node": "Check Site Is Up",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Site Is Up": {
      "main": [
        [
          {
            "node": "Normalize Check Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Check Result": {
      "main": [
        [
          {
            "node": "Track State Changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Track State Changes": {
      "main": [
        [
          {
            "node": "Send WhatsApp Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}
