{
  "name": "GHL AI Appointment Setter",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "ghl-ai-appointment-setter",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook-trigger",
      "name": "Webhook Trigger",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "notesInFlow": true,
      "notes": "GHL/LeadConnector form, chat, SMS, or missed-call webhook"
    },
    {
      "parameters": {
        "jsCode": "const input = $json.body ?? $json;\nconst requiredFields = [\n  \"name\",\n  \"email\",\n  \"phone\",\n  \"message\",\n  \"source\",\n  \"calendarId\",\n  \"locationId\"\n];\nconst missingFields = requiredFields.filter((field) => input[field] === undefined || input[field] === null || input[field] === '');\n\nreturn [{\n  json: {\n    workflow: \"GHL AI Appointment Setter\",\n    trigger: \"GHL/LeadConnector form, chat, SMS, or missed-call webhook\",\n    desiredOutcome: \"Qualify the lead, reply by SMS/email, book a calendar slot, move the opportunity stage, and alert the closer.\",\n    payload: input,\n    missingFields,\n    config: {\n  \"ghlBaseUrl\": \"https://services.leadconnectorhq.com\",\n  \"openAiUrl\": \"https://api.openai.com/v1/responses\",\n  \"slackWebhookUrl\": \"https://hooks.slack.com/services/REPLACE/ME\"\n},\n  },\n}];"
      },
      "id": "normalize-payload",
      "name": "Normalize Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        260,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const data = $json;\nconst payloadText = JSON.stringify(data.payload, null, 2);\nconst actions = [\n  \"create_or_update_contact\",\n  \"qualify_lead\",\n  \"send_sms_or_email_reply\",\n  \"book_calendar_appointment\",\n  \"move_pipeline_stage\",\n  \"notify_closer\"\n];\n\nconst prompt = [\n  \"You are an appointment-setting agent for a local business. Qualify intent, urgency, fit, budget, service need, and scheduling readiness.\",\n  'Return strict JSON with: qualification, score, recommendedActions, messages, risks, humanApprovalRequired.',\n  'Available action types: ' + actions.join(', '),\n  'Payload:',\n  payloadText,\n].join('\\n\\n');\n\n// Replace this stub with an OpenAI, Claude, or AI Agent node when credentials are connected.\nconst scoreBase = data.missingFields.length ? 58 : 82;\nconst humanApprovalRequired = data.missingFields.length > 0 || /refund|legal|angry|urgent|complaint/i.test(payloadText);\n\nreturn [{\n  json: {\n    ...data,\n    ai: {\n      prompt,\n      qualification: data.missingFields.length ? 'needs_more_info' : 'ready_for_action',\n      score: scoreBase,\n      recommendedActions: actions,\n      messages: {\n        internalSummary: `GHL AI Appointment Setter: prepared action plan for ${data.payload.email || data.payload.phone || data.payload.company || 'incoming item'}.`,\n        customerDraft: 'Thanks for reaching out. I have the details and will help with the next step.',\n      },\n      risks: data.missingFields.map((field) => `Missing required field: ${field}`),\n      humanApprovalRequired,\n    },\n  },\n}];"
      },
      "id": "ai-decision-stub",
      "name": "AI Decision Stub",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        520,
        0
      ],
      "notesInFlow": true,
      "notes": "Replace this with a real model node after credentials are connected."
    },
    {
      "parameters": {
        "jsCode": "const now = new Date().toISOString();\nconst tasks = $json.ai.recommendedActions.map((action, index) => ({\n  step: index + 1,\n  action,\n  status: $json.ai.humanApprovalRequired ? 'pending_human_review' : 'ready',\n  idempotencyKey: [$json.workflow, action, $json.payload.email || $json.payload.phone || $json.payload.invoiceNumber || now].join('::'),\n}));\n\nreturn [{\n  json: {\n    ...$json,\n    run: {\n      createdAt: now,\n      mode: 'draft_safe_import',\n      tasks,\n    },\n  },\n}];"
      },
      "id": "build-action-tasks",
      "name": "Build Action Tasks",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        780,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{\n  json: {\n    ok: true,\n    workflow: $json.workflow,\n    mode: $json.run.mode,\n    qualification: $json.ai.qualification,\n    score: $json.ai.score,\n    humanApprovalRequired: $json.ai.humanApprovalRequired,\n    missingFields: $json.missingFields,\n    tasks: $json.run.tasks,\n    customerDraft: $json.ai.messages.customerDraft,\n    internalSummary: $json.ai.messages.internalSummary,\n    activationNote: 'External HTTP/API nodes are present but disabled. Connect credentials and route approved actions before production activation.',\n  },\n}];"
      },
      "id": "audit-and-response-body",
      "name": "Audit and Response Body",
      "type": "n8n-nodes-base.code",
      "typeVersion": 1,
      "position": [
        1040,
        0
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{$json}}",
        "options": {}
      },
      "id": "respond-to-webhook",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        1300,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{$json.config.ghlBaseUrl + \"/contacts/upsert\"}}",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{$json}}",
        "options": {}
      },
      "id": "leadconnector-upsert-contact",
      "name": "LeadConnector - Upsert Contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        520,
        300
      ],
      "disabled": true,
      "notesInFlow": true,
      "notes": "Disabled by default. Attach credentials, verify endpoint schema, then connect into the live path."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{$json.config.ghlBaseUrl + \"/conversations/messages\"}}",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{$json}}",
        "options": {}
      },
      "id": "leadconnector-send-sms-email",
      "name": "LeadConnector - Send SMS/Email",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        780,
        300
      ],
      "disabled": true,
      "notesInFlow": true,
      "notes": "Disabled by default. Attach credentials, verify endpoint schema, then connect into the live path."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{$json.config.ghlBaseUrl + \"/calendars/events/appointments\"}}",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{$json}}",
        "options": {}
      },
      "id": "leadconnector-create-appointment",
      "name": "LeadConnector - Create Appointment",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1040,
        300
      ],
      "disabled": true,
      "notesInFlow": true,
      "notes": "Disabled by default. Attach credentials, verify endpoint schema, then connect into the live path."
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{$json.config.slackWebhookUrl}}",
        "sendBody": true,
        "contentType": "json",
        "specifyBody": "json",
        "jsonBody": "={{$json}}",
        "options": {}
      },
      "id": "slack-notify-closer",
      "name": "Slack - Notify Closer",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 1,
      "position": [
        1300,
        300
      ],
      "disabled": true,
      "notesInFlow": true,
      "notes": "Disabled by default. Attach credentials, verify endpoint schema, then connect into the live path."
    }
  ],
  "connections": {
    "Webhook Trigger": {
      "main": [
        [
          {
            "node": "Normalize Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Payload": {
      "main": [
        [
          {
            "node": "AI Decision Stub",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Decision Stub": {
      "main": [
        [
          {
            "node": "Build Action Tasks",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Action Tasks": {
      "main": [
        [
          {
            "node": "Audit and Response Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Audit and Response Body": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "versionId": "draft",
  "meta": {
    "templateCredsSetupCompleted": false,
    "generatedBy": "Gstack n8n AI Ops Workflow Pack"
  },
  "tags": [
    "ai-ops",
    "starter",
    "gstack"
  ]
}