{
  "name": "Paid Test - Webhook Validate Sheets Email",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "paid-test-lead-intake",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "WebhookLeadIntake",
      "name": "Webhook - Lead Intake",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -820,
        80
      ]
    },
    {
      "parameters": {
        "jsCode": "const body = $json.body || $json;\nconst required = ['source_event_id', 'lead_name', 'email', 'company', 'message'];\nconst missing = required.filter((field) => !body[field] || String(body[field]).trim() === '');\nconst normalized = {\n  source_event_id: String(body.source_event_id || '').trim(),\n  dedupe_key: body.source_event_id ? `lead:${String(body.source_event_id).trim()}` : '',\n  lead_name: String(body.lead_name || '').trim(),\n  email: String(body.email || '').trim().toLowerCase(),\n  company: String(body.company || '').trim(),\n  message: String(body.message || '').trim(),\n  priority: String(body.priority || 'normal').trim().toLowerCase(),\n  submitted_at: body.submitted_at || new Date().toISOString(),\n  received_at: new Date().toISOString(),\n  is_valid: missing.length === 0,\n  missing_fields: missing.join(', ')\n};\nreturn [{ json: normalized }];"
      },
      "id": "ValidateAndNormalize",
      "name": "Validate + Normalize",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -580,
        80
      ]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.is_valid}}",
              "value2": true
            }
          ]
        },
        "options": {}
      },
      "id": "IsPayloadValid",
      "name": "IF - Payload Valid?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        -340,
        80
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": "REPLACE_WITH_GOOGLE_SHEET_ID",
        "sheetName": "Leads",
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "source_event_id": "={{$json.source_event_id}}",
            "dedupe_key": "={{$json.dedupe_key}}",
            "lead_name": "={{$json.lead_name}}",
            "email": "={{$json.email}}",
            "company": "={{$json.company}}",
            "priority": "={{$json.priority}}",
            "message": "={{$json.message}}",
            "submitted_at": "={{$json.submitted_at}}",
            "received_at": "={{$json.received_at}}"
          }
        },
        "options": {}
      },
      "id": "AppendToGoogleSheets",
      "name": "Google Sheets - Append Valid Lead",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        -80,
        -40
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "sendTo": "REPLACE_WITH_OPERATOR_EMAIL",
        "subject": "={{`New ${$json.priority} lead: ${$json.company}`}}",
        "message": "={{`New validated lead received\\n\\nName: ${$json.lead_name}\\nEmail: ${$json.email}\\nCompany: ${$json.company}\\nPriority: ${$json.priority}\\nMessage: ${$json.message}\\n\\nDedupe key: ${$json.dedupe_key}`}}",
        "options": {}
      },
      "id": "SendOperatorEmail",
      "name": "Email - Notify Operator",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        180,
        -40
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { status: 'accepted', source_event_id: $json.source_event_id, dedupe_key: $json.dedupe_key } }}",
        "options": {
          "responseCode": 200
        }
      },
      "id": "RespondAccepted",
      "name": "Respond - Accepted",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        440,
        -40
      ]
    },
    {
      "parameters": {
        "sendTo": "REPLACE_WITH_OPERATOR_EMAIL",
        "subject": "={{`Invalid webhook payload: ${$json.source_event_id || 'missing id'}`}}",
        "message": "={{`A webhook payload was rejected before downstream processing.\\n\\nMissing fields: ${$json.missing_fields}\\nSource event id: ${$json.source_event_id || '(missing)'}\\nCompany: ${$json.company || '(missing)'}\\nReceived at: ${$json.received_at}\\n\\nAction: fix sender payload or update required-field rules.`}}",
        "options": {}
      },
      "id": "SendFailureAlert",
      "name": "Email - Failure Alert",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        -80,
        220
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ { status: 'rejected', missing_fields: $json.missing_fields, source_event_id: $json.source_event_id || null } }}",
        "options": {
          "responseCode": 400
        }
      },
      "id": "RespondRejected",
      "name": "Respond - Rejected",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        180,
        220
      ]
    },
    {
      "parameters": {
        "content": "Production dedupe step to add after credentials are provided:\\n1. Lookup Google Sheet by dedupe_key before append.\\n2. If found, respond already_processed and skip notification.\\n3. If not found, append row and notify.\\nThis prevents webhook retries from creating duplicate operator alerts.",
        "height": 180,
        "width": 340
      },
      "id": "DedupeNote",
      "name": "Sticky - Dedupe / Loop Prevention",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -160,
        -320
      ]
    }
  ],
  "connections": {
    "Webhook - Lead Intake": {
      "main": [
        [
          {
            "node": "Validate + Normalize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate + Normalize": {
      "main": [
        [
          {
            "node": "IF - Payload Valid?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF - Payload Valid?": {
      "main": [
        [
          {
            "node": "Google Sheets - Append Valid Lead",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email - Failure Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Google Sheets - Append Valid Lead": {
      "main": [
        [
          {
            "node": "Email - Notify Operator",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email - Notify Operator": {
      "main": [
        [
          {
            "node": "Respond - Accepted",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email - Failure Alert": {
      "main": [
        [
          {
            "node": "Respond - Rejected",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 1,
  "updatedAt": "2026-06-02T21:40:00.000+08:00",
  "versionId": "paid-test-proof-pack-2026-06-02"
}