{
  "name": "YA-RE-01: Lead Capture Normalizer",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "lead-capture-normalizer",
        "responseMode": "responseNode",
        "options": {}
      },
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [240, 300],
      "id": "node-webhook-inbound",
      "name": "Webhook: Inbound Lead Source"
    },
    {
      "parameters": {
        "jsCode": "// Normalization & Data Sanitization for Real Estate Lead Payloads\nconst item = $input.first().json.body || $input.first().json;\n\n// 1. Clean Name\nconst rawName = String(item.name || item.full_name || '').trim();\nconst cleanName = rawName.replace(/[^\\p{L}\\s'-]/gu, '').replace(/\\s+/g, ' ');\n\n// 2. Clean Email\nconst rawEmail = String(item.email || '').trim().toLowerCase();\nconst emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst validEmail = emailRegex.test(rawEmail) ? rawEmail : null;\n\n// 3. Clean & Format Phone (E.164 normalization logic)\nlet rawPhone = String(item.phone || item.phone_number || '').replace(/[^0-9+]/g, '');\nif (rawPhone.startsWith('00')) rawPhone = '+' + rawPhone.slice(2);\nif (rawPhone.startsWith('01') && rawPhone.length === 11) rawPhone = '+20' + rawPhone.slice(1); // Egypt fallback\nif (rawPhone.startsWith('05') && rawPhone.length === 10) rawPhone = '+966' + rawPhone.slice(1); // KSA fallback\nif (rawPhone.startsWith('5') && rawPhone.length === 9) rawPhone = '+971' + rawPhone; // UAE fallback\nif (!rawPhone.startsWith('+') && rawPhone.length >= 9) rawPhone = '+' + rawPhone;\n\nconst isValidPhone = rawPhone.length >= 10 && rawPhone.length <= 16;\n\n// 4. UTM & Context Defaults\nconst utmSource = String(item.utm_source || item.source || 'direct').toLowerCase();\nconst utmMedium = String(item.utm_medium || 'organic').toLowerCase();\nconst utmCampaign = String(item.utm_campaign || item.campaign_name || 'unassigned');\nconst projectInterested = String(item.project || item.property_interest || 'General Inquiry').trim();\n\n// 5. Validation Flag\nconst isComplete = Boolean(cleanName && validEmail && isValidPhone);\n\nreturn [{\n  json: {\n    normalized_at: new Date().toISOString(),\n    lead_id: item.id || `lead_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`,\n    name: cleanName,\n    email: validEmail,\n    phone: isValidPhone ? rawPhone : null,\n    raw_phone: item.phone || null,\n    project_interest: projectInterested,\n    budget: item.budget || item.price_range || null,\n    country: item.country || 'EG',\n    utm: {\n      source: utmSource,\n      medium: utmMedium,\n      campaign: utmCampaign,\n      term: item.utm_term || null,\n      content: item.utm_content || null\n    },\n    is_valid: isComplete,\n    validation_errors: [\n      !cleanName ? 'missing_name' : null,\n      !validEmail ? 'invalid_email' : null,\n      !isValidPhone ? 'invalid_phone' : null\n    ].filter(Boolean)\n  }\n}];"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [460, 300],
      "id": "node-code-normalize",
      "name": "Code: Normalize & Sanitize"
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.is_valid }}",
              "value2": true
            }
          ]
        }
      },
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [680, 300],
      "id": "node-if-valid",
      "name": "IF: Valid Lead"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"success\",\n  \"message\": \"Lead captured and normalized successfully\",\n  \"lead_id\": \"{{ $json.lead_id }}\"\n}",
        "options": {
          "responseCode": 200
        }
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [900, 200],
      "id": "node-respond-success",
      "name": "Respond: HTTP 200 OK"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://YOUR_CRM_BASE_URL/api/v1/leads",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer YOUR_CRM_API_KEY_HERE"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"name\": \"{{ $json.name }}\",\n  \"email\": \"{{ $json.email }}\",\n  \"phone\": \"{{ $json.phone }}\",\n  \"project_interest\": \"{{ $json.project_interest }}\",\n  \"utm_source\": \"{{ $json.utm.source }}\",\n  \"utm_medium\": \"{{ $json.utm.medium }}\",\n  \"utm_campaign\": \"{{ $json.utm.campaign }}\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1120, 200],
      "id": "node-http-crm-adapter",
      "name": "HTTP: Forward to CRM Adapter"
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\n  \"status\": \"rejected\",\n  \"errors\": {{ JSON.stringify($json.validation_errors) }}\n}",
        "options": {
          "responseCode": 422
        }
      },
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [900, 420],
      "id": "node-respond-reject",
      "name": "Respond: HTTP 422 Invalid Lead"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://YOUR_LOGGING_SERVICE_URL/errors",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"event\": \"invalid_lead_rejected\",\n  \"timestamp\": \"{{ $json.normalized_at }}\",\n  \"errors\": {{ JSON.stringify($json.validation_errors) }},\n  \"raw_lead_id\": \"{{ $json.lead_id }}\"\n}",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [1120, 420],
      "id": "node-http-log-quarantine",
      "name": "HTTP: Log to Quarantine Sink"
    }
  ],
  "connections": {
    "Webhook: Inbound Lead Source": {
      "main": [
        [
          {
            "node": "Code: Normalize & Sanitize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code: Normalize & Sanitize": {
      "main": [
        [
          {
            "node": "IF: Valid Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF: Valid Lead": {
      "main": [
        [
          {
            "node": "Respond: HTTP 200 OK",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Respond: HTTP 422 Invalid Lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond: HTTP 200 OK": {
      "main": [
        [
          {
            "node": "HTTP: Forward to CRM Adapter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond: HTTP 422 Invalid Lead": {
      "main": [
        [
          {
            "node": "HTTP: Log to Quarantine Sink",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "1.0.0",
  "meta": {
    "templateCredsSetupCompleted": false,
    "instanceId": "yehia-ahmed-re-playbook"
  },
  "tags": [
    {
      "name": "Real Estate Marketing"
    },
    {
      "name": "Lead Operations"
    }
  ]
}
