AutomationFlowsWeb Scraping › Js-15b: Donor Cascade Engine

Js-15b: Donor Cascade Engine

JS-15B: Donor Cascade Engine. Uses httpRequest. Webhook trigger; 28 nodes.

Webhook trigger★★★★☆ complexity28 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 28 Complexity: ★★★★☆ Added:

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
{
  "name": "JS-15B: Donor Cascade Engine",
  "nodes": [
    {
      "id": "js15b-001",
      "name": "HTTP Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        250,
        500
      ],
      "parameters": {
        "path": "js15b-cascade",
        "httpMethod": "POST",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "js15b-002",
      "name": "Validate Input",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        500,
        500
      ],
      "parameters": {
        "jsCode": "// Validate incoming payload from JS-15 or /api/n8n/cascade-trigger\nconst body = $input.first().json.body || $input.first().json;\nconst blood_request_id = body.blood_request_id;\nconst urgency = body.urgency || 'routine';\n\nif (!blood_request_id) {\n  throw new Error('Missing blood_request_id in cascade trigger payload');\n}\n\nif (!['routine', 'urgent', 'critical'].includes(urgency)) {\n  throw new Error(`Invalid urgency: ${urgency}. Must be routine|urgent|critical`);\n}\n\nreturn [{ json: { blood_request_id, urgency } }];"
      }
    },
    {
      "id": "js15b-003",
      "name": "Switch on Urgency",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        750,
        500
      ],
      "parameters": {
        "dataType": "string",
        "value1": "={{ $json.urgency }}",
        "rules": {
          "rules": [
            {
              "value2": "routine",
              "outputKey": "routine"
            },
            {
              "value2": "urgent",
              "outputKey": "urgent"
            },
            {
              "value2": "critical",
              "outputKey": "critical"
            }
          ]
        },
        "fallbackOutput": "extra"
      }
    },
    {
      "id": "js15b-010",
      "name": "Routine Wait 30min",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1050,
        200
      ],
      "parameters": {
        "amount": 30,
        "unit": "minutes"
      }
    },
    {
      "id": "js15b-011",
      "name": "Routine Check Fulfilled T2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1300,
        200
      ],
      "parameters": {
        "jsCode": "// Check if blood request is already fulfilled before Tier 2\n// Reads blood_requests row and checks donors_confirmed_count >= units_needed\nconst blood_request_id = $json.blood_request_id;\nconst urgency = $json.urgency;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\n\ntry {\n  const res = await fetch(`${API_BASE}/api/blood-requests/${blood_request_id}`, {\n    headers: { 'X-N8N-SECRET': SECRET }\n  });\n  const data = await res.json();\n  const req = data.data || data;\n  \n  if (req.donors_confirmed_count >= req.units_needed) {\n    // Fulfilled \u2014 stop cascade\n    return [];\n  }\n  \n  return [{ json: { blood_request_id, urgency, tier: 2, scope: 'block', count: 5, blood_group: req.blood_group, district: req.district, block: req.block, units_needed: req.units_needed } }];\n} catch (err) {\n  // On error, continue cascade (fail-open for donor safety)\n  return [{ json: { blood_request_id, urgency, tier: 2, scope: 'block', count: 5 } }];\n}"
      }
    },
    {
      "id": "js15b-012",
      "name": "Routine Tier 2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1550,
        200
      ],
      "parameters": {
        "jsCode": "// Tier 2 (routine): next 5 same-block donors\n// Internal sequence:\n// 1. Read blood_requests row (fulfilled check done in prior node)\n// 2. Read cascade_notifications (already-notified set)\n// 3. Call find_matching_donors (exclude notified)\n// 4. Slice top-5 for this tier\n// 5. INSERT cascade_notifications rows ON CONFLICT DO NOTHING\n// 6. INSERT blood_donor_responses (pending)\n// 7. Send WhatsApp HAAN/NAHI buttons\n// 8. UPDATE blood_requests cascade_tier = 2\n\nconst { blood_request_id, urgency, blood_group, district, block, units_needed } = $json;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\nconst TIER = 2;\nconst COUNT = 5;\nconst SCOPE = 'block';\n\ntry {\n  // Find matching donors excluding already-notified\n  const findRes = await fetch(`${API_BASE}/api/blood-requests/find-donors`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, scope: SCOPE, exclude_notified: true, limit: COUNT })\n  });\n  const donors = (await findRes.json()).data || [];\n  \n  if (donors.length === 0) {\n    return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: 0 } }];\n  }\n  \n  // Insert cascade_notifications + blood_donor_responses + send WhatsApp\n  const notifyRes = await fetch(`${API_BASE}/api/n8n/cascade-notify-batch`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, tier: TIER, donor_ids: donors.map(d => d.id) })\n  });\n  \n  // Update cascade_tier\n  await fetch(`${API_BASE}/api/blood-requests/update-cascade-tier`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, cascade_tier: TIER })\n  });\n  \n  return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: donors.length, completed: true } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: TIER, error: err.message } }];\n}"
      }
    },
    {
      "id": "js15b-013",
      "name": "Routine Wait 2hr",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1800,
        200
      ],
      "parameters": {
        "amount": 2,
        "unit": "hours"
      }
    },
    {
      "id": "js15b-014",
      "name": "Routine Check Fulfilled T3",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2050,
        200
      ],
      "parameters": {
        "jsCode": "// Check if blood request is already fulfilled before Tier 3\nconst blood_request_id = $json.blood_request_id;\nconst urgency = $json.urgency;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\n\ntry {\n  const res = await fetch(`${API_BASE}/api/blood-requests/${blood_request_id}`, {\n    headers: { 'X-N8N-SECRET': SECRET }\n  });\n  const data = await res.json();\n  const req = data.data || data;\n  \n  if (req.donors_confirmed_count >= req.units_needed) {\n    return []; // Fulfilled \u2014 stop cascade\n  }\n  \n  return [{ json: { blood_request_id, urgency, tier: 3, scope: 'district', count: 10, blood_group: req.blood_group, district: req.district, units_needed: req.units_needed } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: 3, scope: 'district', count: 10 } }];\n}"
      }
    },
    {
      "id": "js15b-015",
      "name": "Routine Tier 3",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        200
      ],
      "parameters": {
        "jsCode": "// Tier 3 (routine): next 10 district-wide donors\n// Same internal sequence as Tier 2 but scope=district, count=10\nconst { blood_request_id, urgency } = $json;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\nconst TIER = 3;\nconst COUNT = 10;\nconst SCOPE = 'district';\n\ntry {\n  const findRes = await fetch(`${API_BASE}/api/blood-requests/find-donors`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, scope: SCOPE, exclude_notified: true, limit: COUNT })\n  });\n  const donors = (await findRes.json()).data || [];\n  \n  if (donors.length === 0) {\n    return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: 0 } }];\n  }\n  \n  const notifyRes = await fetch(`${API_BASE}/api/n8n/cascade-notify-batch`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, tier: TIER, donor_ids: donors.map(d => d.id) })\n  });\n  \n  await fetch(`${API_BASE}/api/blood-requests/update-cascade-tier`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, cascade_tier: TIER })\n  });\n  \n  return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: donors.length, completed: true } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: TIER, error: err.message } }];\n}"
      }
    },
    {
      "id": "js15b-016",
      "name": "Routine Wait 6hr",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        2550,
        200
      ],
      "parameters": {
        "amount": 6,
        "unit": "hours"
      }
    },
    {
      "id": "js15b-017",
      "name": "Routine Check Fulfilled T4",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2800,
        200
      ],
      "parameters": {
        "jsCode": "// Check if blood request is already fulfilled before Tier 4\nconst blood_request_id = $json.blood_request_id;\nconst urgency = $json.urgency;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\n\ntry {\n  const res = await fetch(`${API_BASE}/api/blood-requests/${blood_request_id}`, {\n    headers: { 'X-N8N-SECRET': SECRET }\n  });\n  const data = await res.json();\n  const req = data.data || data;\n  \n  if (req.donors_confirmed_count >= req.units_needed) {\n    return []; // Fulfilled \u2014 stop cascade\n  }\n  \n  return [{ json: { blood_request_id, urgency, tier: 4, scope: 'adjacent', count: 0, blood_group: req.blood_group, district: req.district, units_needed: req.units_needed } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: 4, scope: 'adjacent', count: 0 } }];\n}"
      }
    },
    {
      "id": "js15b-018",
      "name": "Routine Tier 4",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3050,
        200
      ],
      "parameters": {
        "jsCode": "// Tier 4 (routine): adjacent districts \u2014 all matching donors\n// scope=adjacent, count=0 (all matching)\nconst { blood_request_id, urgency } = $json;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\nconst TIER = 4;\nconst SCOPE = 'adjacent';\n\ntry {\n  const findRes = await fetch(`${API_BASE}/api/blood-requests/find-donors`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, scope: SCOPE, exclude_notified: true, limit: 999 })\n  });\n  const donors = (await findRes.json()).data || [];\n  \n  if (donors.length === 0) {\n    return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: 0 } }];\n  }\n  \n  await fetch(`${API_BASE}/api/n8n/cascade-notify-batch`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, tier: TIER, donor_ids: donors.map(d => d.id) })\n  });\n  \n  await fetch(`${API_BASE}/api/blood-requests/update-cascade-tier`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, cascade_tier: TIER })\n  });\n  \n  return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: donors.length, completed: true } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: TIER, error: err.message } }];\n}"
      }
    },
    {
      "id": "js15b-020",
      "name": "Urgent Wait 15min",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1050,
        500
      ],
      "parameters": {
        "amount": 15,
        "unit": "minutes"
      }
    },
    {
      "id": "js15b-021",
      "name": "Urgent Check Fulfilled T2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1300,
        500
      ],
      "parameters": {
        "jsCode": "// Check if blood request is already fulfilled before Tier 2 (urgent)\nconst blood_request_id = $json.blood_request_id;\nconst urgency = $json.urgency;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\n\ntry {\n  const res = await fetch(`${API_BASE}/api/blood-requests/${blood_request_id}`, {\n    headers: { 'X-N8N-SECRET': SECRET }\n  });\n  const data = await res.json();\n  const req = data.data || data;\n  \n  if (req.donors_confirmed_count >= req.units_needed) {\n    return []; // Fulfilled \u2014 stop cascade\n  }\n  \n  return [{ json: { blood_request_id, urgency, tier: 2, scope: 'block', count: 10, blood_group: req.blood_group, district: req.district, block: req.block, units_needed: req.units_needed } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: 2, scope: 'block', count: 10 } }];\n}"
      }
    },
    {
      "id": "js15b-022",
      "name": "Urgent Tier 2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1550,
        500
      ],
      "parameters": {
        "jsCode": "// Tier 2 (urgent): next 10 same-block donors\nconst { blood_request_id, urgency } = $json;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\nconst TIER = 2;\nconst COUNT = 10;\nconst SCOPE = 'block';\n\ntry {\n  const findRes = await fetch(`${API_BASE}/api/blood-requests/find-donors`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, scope: SCOPE, exclude_notified: true, limit: COUNT })\n  });\n  const donors = (await findRes.json()).data || [];\n  \n  if (donors.length === 0) {\n    return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: 0 } }];\n  }\n  \n  await fetch(`${API_BASE}/api/n8n/cascade-notify-batch`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, tier: TIER, donor_ids: donors.map(d => d.id) })\n  });\n  \n  await fetch(`${API_BASE}/api/blood-requests/update-cascade-tier`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, cascade_tier: TIER })\n  });\n  \n  return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: donors.length, completed: true } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: TIER, error: err.message } }];\n}"
      }
    },
    {
      "id": "js15b-023",
      "name": "Urgent Wait 1hr",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1800,
        500
      ],
      "parameters": {
        "amount": 1,
        "unit": "hours"
      }
    },
    {
      "id": "js15b-024",
      "name": "Urgent Check Fulfilled T3",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2050,
        500
      ],
      "parameters": {
        "jsCode": "// Check if blood request is already fulfilled before Tier 3 (urgent)\nconst blood_request_id = $json.blood_request_id;\nconst urgency = $json.urgency;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\n\ntry {\n  const res = await fetch(`${API_BASE}/api/blood-requests/${blood_request_id}`, {\n    headers: { 'X-N8N-SECRET': SECRET }\n  });\n  const data = await res.json();\n  const req = data.data || data;\n  \n  if (req.donors_confirmed_count >= req.units_needed) {\n    return []; // Fulfilled \u2014 stop cascade\n  }\n  \n  return [{ json: { blood_request_id, urgency, tier: 3, scope: 'district', count: 20, blood_group: req.blood_group, district: req.district, units_needed: req.units_needed } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: 3, scope: 'district', count: 20 } }];\n}"
      }
    },
    {
      "id": "js15b-025",
      "name": "Urgent Tier 3",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2300,
        500
      ],
      "parameters": {
        "jsCode": "// Tier 3 (urgent): next 20 district-wide donors\nconst { blood_request_id, urgency } = $json;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\nconst TIER = 3;\nconst COUNT = 20;\nconst SCOPE = 'district';\n\ntry {\n  const findRes = await fetch(`${API_BASE}/api/blood-requests/find-donors`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, scope: SCOPE, exclude_notified: true, limit: COUNT })\n  });\n  const donors = (await findRes.json()).data || [];\n  \n  if (donors.length === 0) {\n    return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: 0 } }];\n  }\n  \n  await fetch(`${API_BASE}/api/n8n/cascade-notify-batch`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, tier: TIER, donor_ids: donors.map(d => d.id) })\n  });\n  \n  await fetch(`${API_BASE}/api/blood-requests/update-cascade-tier`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, cascade_tier: TIER })\n  });\n  \n  return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: donors.length, completed: true } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: TIER, error: err.message } }];\n}"
      }
    },
    {
      "id": "js15b-030",
      "name": "Critical Tier 1 SOS",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1050,
        800
      ],
      "parameters": {
        "jsCode": "// Tier 1 (critical): SOS all-district fan-out \u2014 ALL matching donors\n// For critical urgency, Tier 1 is the SOS broadcast to entire district\n// scope=district, count=0 (all matching donors)\nconst { blood_request_id, urgency } = $json;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\nconst TIER = 1;\nconst SCOPE = 'district';\n\ntry {\n  const findRes = await fetch(`${API_BASE}/api/blood-requests/find-donors`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, scope: SCOPE, exclude_notified: true, limit: 999 })\n  });\n  const donors = (await findRes.json()).data || [];\n  \n  if (donors.length === 0) {\n    return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: 0 } }];\n  }\n  \n  await fetch(`${API_BASE}/api/n8n/cascade-notify-batch`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, tier: TIER, donor_ids: donors.map(d => d.id) })\n  });\n  \n  await fetch(`${API_BASE}/api/blood-requests/update-cascade-tier`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, cascade_tier: TIER })\n  });\n  \n  return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: donors.length, completed: true } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: TIER, error: err.message } }];\n}"
      }
    },
    {
      "id": "js15b-031",
      "name": "Critical Wait 30min",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1300,
        800
      ],
      "parameters": {
        "amount": 30,
        "unit": "minutes"
      }
    },
    {
      "id": "js15b-032",
      "name": "Critical Check Fulfilled T2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1550,
        800
      ],
      "parameters": {
        "jsCode": "// Check if blood request is already fulfilled before Tier 2 (critical)\nconst blood_request_id = $json.blood_request_id;\nconst urgency = $json.urgency;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\n\ntry {\n  const res = await fetch(`${API_BASE}/api/blood-requests/${blood_request_id}`, {\n    headers: { 'X-N8N-SECRET': SECRET }\n  });\n  const data = await res.json();\n  const req = data.data || data;\n  \n  if (req.donors_confirmed_count >= req.units_needed) {\n    return []; // Fulfilled \u2014 stop cascade\n  }\n  \n  return [{ json: { blood_request_id, urgency, tier: 2, scope: 'adjacent', count: 0, blood_group: req.blood_group, district: req.district, units_needed: req.units_needed } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: 2, scope: 'adjacent', count: 0 } }];\n}"
      }
    },
    {
      "id": "js15b-033",
      "name": "Critical Tier 2",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1800,
        800
      ],
      "parameters": {
        "jsCode": "// Tier 2 (critical): adjacent districts \u2014 ALL matching donors\nconst { blood_request_id, urgency } = $json;\nconst API_BASE = $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app';\nconst SECRET = $env.N8N_WEBHOOK_SECRET;\nconst TIER = 2;\nconst SCOPE = 'adjacent';\n\ntry {\n  const findRes = await fetch(`${API_BASE}/api/blood-requests/find-donors`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, scope: SCOPE, exclude_notified: true, limit: 999 })\n  });\n  const donors = (await findRes.json()).data || [];\n  \n  if (donors.length === 0) {\n    return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: 0 } }];\n  }\n  \n  await fetch(`${API_BASE}/api/n8n/cascade-notify-batch`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, tier: TIER, donor_ids: donors.map(d => d.id) })\n  });\n  \n  await fetch(`${API_BASE}/api/blood-requests/update-cascade-tier`, {\n    method: 'POST',\n    headers: { 'Content-Type': 'application/json', 'X-N8N-SECRET': SECRET },\n    body: JSON.stringify({ blood_request_id, cascade_tier: TIER })\n  });\n  \n  return [{ json: { blood_request_id, urgency, tier: TIER, donors_notified: donors.length, completed: true } }];\n} catch (err) {\n  return [{ json: { blood_request_id, urgency, tier: TIER, error: err.message } }];\n}"
      }
    },
    {
      "id": "js15b-040",
      "name": "Cascade End",
      "type": "n8n-nodes-base.noOp",
      "typeVersion": 1,
      "position": [
        3300,
        500
      ],
      "parameters": {}
    },
    {
      "id": "js15b-050",
      "name": "Respond OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        750,
        700
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ ok: true, message: 'Cascade triggered', blood_request_id: $json.blood_request_id, urgency: $json.urgency }) }}"
      }
    },
    {
      "id": "js15b-060",
      "name": "Tier Logic - Read Request",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        250,
        1200
      ],
      "parameters": {
        "method": "GET",
        "url": "={{ $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app' }}/api/blood-requests/{{ $json.blood_request_id }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-N8N-SECRET",
              "value": "={{ $env.N8N_WEBHOOK_SECRET }}"
            }
          ]
        },
        "options": {}
      },
      "notes": "Shared reference node \u2014 each tier Code Node calls this pattern internally via HTTP Request"
    },
    {
      "id": "js15b-061",
      "name": "Tier Logic - Find Donors",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        500,
        1200
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app' }}/api/blood-requests/find-donors",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "X-N8N-SECRET",
              "value": "={{ $env.N8N_WEBHOOK_SECRET }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ blood_request_id: $json.blood_request_id, scope: $json.scope, exclude_notified: true, limit: $json.count || 999 }) }}",
        "options": {}
      },
      "notes": "Shared reference \u2014 find matching donors excluding already-notified"
    },
    {
      "id": "js15b-062",
      "name": "Tier Logic - Notify Donors WhatsApp",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        750,
        1200
      ],
      "parameters": {
        "method": "POST",
        "url": "https://graph.facebook.com/v21.0/{{ $env.WHATSAPP_PHONE_NUMBER_ID || 'TODO_PHONE_NUMBER_ID' }}/messages",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer {{ $env.WHATSAPP_ACCESS_TOKEN || 'TODO_ACCESS_TOKEN' }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ messaging_product: 'whatsapp', to: $json.donor_phone, type: 'interactive', interactive: { type: 'button', header: { type: 'text', text: '\ud83e\ude78 Blood Request Alert' }, body: { text: $json.notification_message || 'A patient needs ' + $json.blood_group + ' blood. Can you donate? Reply HAAN (Yes) or NAHI (No).' }, action: { buttons: [{ type: 'reply', reply: { id: 'HAAN_' + $json.blood_request_id, title: 'HAAN \u2705' } }, { type: 'reply', reply: { id: 'NAHI_' + $json.blood_request_id, title: 'NAHI \u274c' } }] } } }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "notes": "Shared reference \u2014 WhatsApp HAAN/NAHI interactive button template"
    },
    {
      "id": "js15b-063",
      "name": "Tier Logic - Update Cascade Tier",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1000,
        1200
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $env.API_BASE_URL || 'https://wb-grievance-portal.vercel.app' }}/api/blood-requests/update-cascade-tier",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "X-N8N-SECRET",
              "value": "={{ $env.N8N_WEBHOOK_SECRET }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ blood_request_id: $json.blood_request_id, cascade_tier: $json.tier }) }}",
        "options": {}
      },
      "notes": "Shared reference \u2014 UPDATE blood_requests SET cascade_tier = N"
    }
  ],
  "connections": {
    "HTTP Trigger": {
      "main": [
        [
          {
            "node": "Validate Input",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate Input": {
      "main": [
        [
          {
            "node": "Switch on Urgency",
            "type": "main",
            "index": 0
          },
          {
            "node": "Respond OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Switch on Urgency": {
      "main": [
        [
          {
            "node": "Routine Wait 30min",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Urgent Wait 15min",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Critical Tier 1 SOS",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routine Wait 30min": {
      "main": [
        [
          {
            "node": "Routine Check Fulfilled T2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routine Check Fulfilled T2": {
      "main": [
        [
          {
            "node": "Routine Tier 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routine Tier 2": {
      "main": [
        [
          {
            "node": "Routine Wait 2hr",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routine Wait 2hr": {
      "main": [
        [
          {
            "node": "Routine Check Fulfilled T3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routine Check Fulfilled T3": {
      "main": [
        [
          {
            "node": "Routine Tier 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routine Tier 3": {
      "main": [
        [
          {
            "node": "Routine Wait 6hr",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routine Wait 6hr": {
      "main": [
        [
          {
            "node": "Routine Check Fulfilled T4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routine Check Fulfilled T4": {
      "main": [
        [
          {
            "node": "Routine Tier 4",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Routine Tier 4": {
      "main": [
        [
          {
            "node": "Cascade End",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Urgent Wait 15min": {
      "main": [
        [
          {
            "node": "Urgent Check Fulfilled T2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Urgent Check Fulfilled T2": {
      "main": [
        [
          {
            "node": "Urgent Tier 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Urgent Tier 2": {
      "main": [
        [
          {
            "node": "Urgent Wait 1hr",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Urgent Wait 1hr": {
      "main": [
        [
          {
            "node": "Urgent Check Fulfilled T3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Urgent Check Fulfilled T3": {
      "main": [
        [
          {
            "node": "Urgent Tier 3",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Urgent Tier 3": {
      "main": [
        [
          {
            "node": "Cascade End",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Critical Tier 1 SOS": {
      "main": [
        [
          {
            "node": "Critical Wait 30min",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Critical Wait 30min": {
      "main": [
        [
          {
            "node": "Critical Check Fulfilled T2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Critical Check Fulfilled T2": {
      "main": [
        [
          {
            "node": "Critical Tier 2",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Critical Tier 2": {
      "main": [
        [
          {
            "node": "Cascade End",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "meta": {
    "description": "JS-15B: Donor Cascade Engine \u2014 Tiered donor notification workflow triggered by JS-15 (via HTTP webhook). Handles routine/urgent/critical urgency levels with progressive tier escalation and wait nodes between tiers. Each tier checks fulfillment before notifying the next batch of donors.",
    "templateCredsSetupCompleted": false
  }
}

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

JS-15B: Donor Cascade Engine. Uses httpRequest. Webhook trigger; 28 nodes.

Source: https://github.com/mahatosnehabala250-project/wb-grievance-portal/blob/a6f924dd4fafc5427b251210aec644a878a5510d/n8n-workflows/JS-15B.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

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

Web Scraping

This n8n template provides enterprise-level version control for your workflows using GitHub integration. Stop losing hours to broken workflows and manual exports – get proper commit history, visual di

n8n, Execute Workflow Trigger, HTTP Request +1
Web Scraping

This flow creates dummy files for every item added in your *Arrs (Radarr/Sonarr) with the tag .

HTTP Request, Ssh
Web Scraping

eek-Go v2 (Batch-Then-Review). Uses httpRequest. Webhook trigger; 75 nodes.

HTTP Request
Web Scraping

This workflow receives webhook requests from a content calendar and uses the X API v2 to publish text posts, threads, image/video posts, and polls, as well as delete existing posts and run a credentia

HTTP Request
Web Scraping

This workflow acts as a central API gateway for all technical indicator agents in the Binance Spot Market Quant AI system. It listens for incoming webhook requests and dynamically routes them to the c

HTTP Request