AutomationFlowsWeb Scraping › G — Off-market Lead Ingestor (apify → Portal Api)

G — Off-market Lead Ingestor (apify → Portal Api)

G — Off-Market Lead Ingestor (Apify → Portal API). Uses httpRequest. Webhook trigger; 7 nodes.

Webhook trigger★★★★☆ complexity7 nodesHTTP Request
Web Scraping Trigger: Webhook Nodes: 7 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": "G \u2014 Off-Market Lead Ingestor (Apify \u2192 Portal API)",
  "description": "Recebe leads do Apify via webhook, normaliza e chama POST /api/offmarket-leads/manual (auto-pipeline completo: score + price-intel + match-buyers + deal-eval). Nunca escreve directamente no Supabase.",
  "active": true,
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "offmarket-new",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "g001",
      "name": "Webhook \u2014 Novo Lead Off-Market",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const raw = $input.first().json;\nconst body = raw.body || raw;\n// Normalizar para formato esperado por /api/offmarket-leads/manual\nconst lead = {\n  nome: body.nome || body.title || body.address || 'Lead Apify ' + new Date().toISOString().substring(0,10),\n  cidade: body.cidade || body.city || body.location || 'Lisboa',\n  tipo_ativo: body.tipo_ativo || body.propertyType || 'apartamento',\n  // Campos opcionais enriquecidos\n  localizacao: body.localizacao || body.morada || body.address || null,\n  price_ask: body.price_ask || body.price ? Number(body.price_ask || body.price) : null,\n  area_m2: body.area_m2 || body.area ? Number(body.area_m2 || body.area) : null,\n  contacto: body.contacto || body.contact || null,\n  owner_name: body.owner_name || body.owner || null,\n  contact_phone_owner: body.contact_phone_owner || body.phone || null,\n  source: body.source || 'apify',\n  urgency: body.urgency || 'unknown',\n  notes: body.notes || body.notas || body.description\n    ? (body.notes || body.notas || body.description || '').substring(0, 500)\n    : null\n};\n// Limpar nulos\nObject.keys(lead).forEach(k => { if (lead[k] === null) delete lead[k]; });\nreturn [{ json: lead }];"
      },
      "id": "g002",
      "name": "Code \u2014 Normalizar Lead",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        420,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.PORTAL_URL }}/api/offmarket-leads/manual",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-cron-secret",
              "value": "={{ $env.CRON_SECRET }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify($json) }}",
        "options": {}
      },
      "id": "g003",
      "name": "Portal API \u2014 Criar Lead + Auto-Pipeline",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        640,
        300
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const resp = $input.first().json;\n// Portal API retorna: { success, lead_id, score, nome, cidade, ... }\nif (!resp.success) {\n  console.log('[WF-G] Portal API error:', JSON.stringify(resp).substring(0, 300));\n  return [{ json: { id: null, _error: true, _raw: JSON.stringify(resp).substring(0, 200) } }];\n}\n// score j\u00e1 calculado pelo pipeline completo\nconst score = resp.score ?? 0;\nconst tier = score >= 70 ? 'A' : score >= 40 ? 'B' : 'C';\nconsole.log('[WF-G] Lead criado:', resp.lead_id, 'score:', score, 'tier:', tier);\nreturn [{ json: { ...resp, id: resp.lead_id, computed_score: score, tier } }];"
      },
      "id": "g004",
      "name": "Code \u2014 Extrair Score do Pipeline",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        860,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{ $json.computed_score }}",
              "operation": "largerEqual",
              "value2": 70
            }
          ]
        }
      },
      "id": "g005",
      "name": "IF \u2014 Score \u226570?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 1,
      "position": [
        1080,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.resend.com/emails",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $env.RESEND_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "raw",
        "rawContentType": "application/json",
        "body": "={{ JSON.stringify({ from: 'Deal Machine <noreply@agencygroup.pt>', to: ['geral@agencygroup.pt'], subject: '\ud83d\udd25 Off-Market Score Alto: ' + $json.nome + ' (' + $json.computed_score + '/100 Tier ' + $json.tier + ')', text: 'LEAD OFF-MARKET\\n\\nNome: ' + $json.nome + '\\nScore: ' + $json.computed_score + '/100 Tier ' + $json.tier + '\\nCidade: ' + ($json.cidade||'\u2014') + '\\nPre\u00e7o: ' + ($json.price_ask ? '\u20ac' + Number($json.price_ask/1000).toFixed(0) + 'K' : '\u2014') + '\\nFonte: ' + ($json.source||'apify') + '\\nPortal: ' + $env.PORTAL_URL + '/portal' }) }}",
        "options": {}
      },
      "id": "g006",
      "name": "Email \u2014 Alerta Score Alto",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1300,
        200
      ],
      "continueOnFail": true
    },
    {
      "parameters": {
        "jsCode": "const lead = $input.first().json;\nreturn [{ json: { status: 'ok', id: lead.id || lead.lead_id, score: lead.computed_score, tier: lead.tier, nome: lead.nome } }];"
      },
      "id": "g007",
      "name": "Code \u2014 Done",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1300,
        400
      ]
    }
  ],
  "connections": {
    "Webhook \u2014 Novo Lead Off-Market": {
      "main": [
        [
          {
            "node": "Code \u2014 Normalizar Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 Normalizar Lead": {
      "main": [
        [
          {
            "node": "Portal API \u2014 Criar Lead + Auto-Pipeline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Portal API \u2014 Criar Lead + Auto-Pipeline": {
      "main": [
        [
          {
            "node": "Code \u2014 Extrair Score do Pipeline",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 Extrair Score do Pipeline": {
      "main": [
        [
          {
            "node": "IF \u2014 Score \u226570?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF \u2014 Score \u226570?": {
      "main": [
        [
          {
            "node": "Email \u2014 Alerta Score Alto",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Code \u2014 Done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email \u2014 Alerta Score Alto": {
      "main": [
        [
          {
            "node": "Code \u2014 Done",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveDataErrorExecution": "all",
    "saveDataSuccessExecution": "all",
    "saveManualExecutions": true,
    "callerPolicy": "workflowsFromSameOwner"
  },
  "tags": [
    {
      "name": "offmarket"
    },
    {
      "name": "core"
    }
  ]
}
Pro

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

About this workflow

G — Off-Market Lead Ingestor (Apify → Portal API). Uses httpRequest. Webhook trigger; 7 nodes.

Source: https://github.com/cfeiteira73-cmd/agency-group-platform/blob/4d1d3507093100c61ec89737a20e8a750ef06f9f/n8n-workflows/wf_g_current.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 workflow enables the submission of business-critical URLs via the Google Indexing API and IndexNow.

HTTP Request, XML
Web Scraping

Never miss important website updates again! This workflow automatically tracks changes on dynamic websites (think React apps, JavaScript-heavy sites) and sends you instant email notifications when som

HTTP Request, Google Sheets, Gmail
Web Scraping

This template implements a recursive web crawler inside n8n. Starting from a given URL, it crawls linked pages up to a maximum depth (default: 3), extracts text and links, and returns the collected co

HTTP Request
Web Scraping

Crawl Space & Foundation Repair Intake AI - Vapi MVP (Client Template). Uses httpRequest, googleSheets. Webhook trigger; 14 nodes.

HTTP Request, Google Sheets
Web Scraping

JD Scraper. Uses httpRequest. Webhook trigger; 8 nodes.

HTTP Request