{
  "name": "Uptime monitor that pings you on WhatsApp when a site goes down (and when it's back)",
  "nodes": [
    {
      "parameters": {
        "content": "What 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. Create an API key at https://developers.wasync.app/keys (scopes: whatsapp.send).\n2. In n8n, go to Credentials -> New -> Header Auth. Name the credential \"WASync API key\". Set its Name field to `Authorization` and its Value field to `Bearer wsk_live_<your key>`.\n3. Open the \"Settings\" node at the top of this workflow and fill in your own values (the list of sites, the connection id, and the phone number to alert). Never commit real portal URLs, tokens, connection ids, or phone numbers.\n\nThis template uses only built-in n8n nodes, so it works on n8n Cloud; if you self-host, you can also use our community node n8n-nodes-wasync.\n\nDocs: https://docs.wasync.app/docs/guides/n8n?utm_source=n8n&utm_medium=template&utm_campaign=t7-http",
        "height": 620,
        "width": 560
      },
      "name": "Read Me First",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        -180
      ]
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      },
      "name": "Every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.4,
      "position": [
        0,
        460
      ]
    },
    {
      "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": [
        260,
        460
      ]
    },
    {
      "parameters": {
        "fieldToSplitOut": "sites",
        "options": {
          "destinationFieldName": "checkedUrl"
        }
      },
      "name": "Split Sites",
      "type": "n8n-nodes-base.splitOut",
      "typeVersion": 1,
      "position": [
        520,
        460
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $json.checkedUrl }}",
        "options": {
          "timeout": 10000,
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        }
      },
      "name": "Check Site",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        780,
        460
      ],
      "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": [
        1040,
        460
      ]
    },
    {
      "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": [
        1300,
        460
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://developers.wasync.app/api/v1/messages",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "options": {},
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ connectionId: $('Settings').item.json.wasyncConnectionId, to: $('Settings').item.json.alertTo, text: $json.text }) }}"
      },
      "name": "Send WhatsApp Alert",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1560,
        460
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "WASync API key"
        }
      }
    }
  ],
  "connections": {
    "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",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Site": {
      "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"
  }
}
