{
  "name": "Light AI quote approved → HighLevel",
  "nodes": [
    {
      "parameters": {
        "content": "## Quote approved → HighLevel\n1. Subscribe the production webhook URL in Light AI to quote.approved and paste the secret into \"Verify signature\".\n2. Create a Header Auth credential \"HighLevel\": Authorization = Bearer <private integration token>.\n3. Partners: set each grant's external_reference to the HighLevel locationId; otherwise set LOCATION_ID below.\nThe endpoint needs clients:read for names, e-mails and phones.\nDocs: https://docs.light-ai.app/integrations/highlevel",
        "height": 280,
        "width": 380
      },
      "name": "About",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -340
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "light-ai-highlevel",
        "responseMode": "onReceived",
        "options": {
          "rawBody": true
        }
      },
      "name": "Light AI webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "light-ai-highlevel"
    },
    {
      "parameters": {
        "jsCode": "// n8n Code node (\"Run Once for All Items\"), right after a Webhook node\n// with Options → Raw Body turned on. n8n Cloud allows the crypto module;\n// self-hosted n8n needs NODE_FUNCTION_ALLOW_BUILTIN=crypto.\nconst crypto = require('crypto');\nconst secret = 'whsec_…'; // or $env.LIGHT_AI_WEBHOOK_SECRET on self-hosted n8n\n\nconst headers = $input.first().json.headers;\nconst raw = (await this.helpers.getBinaryDataBuffer(0, 'data')).toString('utf8');\nconst id = headers['webhook-id'];\nconst timestamp = headers['webhook-timestamp'];\nif (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) throw new Error('timestamp outside tolerance');\n\nconst key = Buffer.from(secret.replace(/^whsec_/, ''), 'base64');\nconst expected = crypto.createHmac('sha256', key).update(`${id}.${timestamp}.${raw}`).digest('base64');\nconst valid = headers['webhook-signature'].split(' ')\n  .some((entry) => entry === `v1,${expected}`);\nif (!valid) throw new Error('invalid Light AI signature');\n\nreturn [{ json: { webhook_id: id, event: JSON.parse(raw) } }];"
      },
      "name": "Verify signature",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Skip other events and the writes this workflow caused itself (loop prevention).\nreturn $input.all().filter((item) => {\n  const event = item.json.event;\n  return event.type === 'quote.approved' && event.origin?.source_system !== 'highlevel';\n});"
      },
      "name": "Only quote.approved",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "// Name, e-mail and phone arrive when the endpoint's credential holds clients:read.\nconst event = $input.first().json.event;\nconst quote = event.data.object;\nconst client = quote.client ?? {};\nconst address = quote.property?.address?.formatted ?? '';\nconst name = [client.first_name, client.last_name].filter(Boolean).join(' ') || 'Light AI customer';\nconst LOCATION_ID = 'YOUR_LOCATION_ID';\nreturn [{ json: { contact: {\n  locationId: event.grant?.external_reference ?? LOCATION_ID,\n  firstName: client.first_name ?? undefined,\n  lastName: client.last_name ?? undefined,\n  name,\n  email: client.email ?? undefined,\n  phone: client.phone ?? undefined,\n  address1: quote.property?.address?.line1 ?? undefined,\n  city: quote.property?.address?.city ?? undefined,\n  state: quote.property?.address?.region ?? undefined,\n  postalCode: quote.property?.address?.postal_code ?? undefined,\n  source: 'Light AI',\n  tags: ['light-ai-approved'],\n} } }];"
      },
      "name": "Build contact",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://services.leadconnectorhq.com/contacts/upsert",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Version",
              "value": "2021-07-28"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.contact) }}",
        "options": {}
      },
      "name": "Upsert HighLevel contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        0
      ]
    }
  ],
  "connections": {
    "Light AI webhook": {
      "main": [
        [
          {
            "node": "Verify signature",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verify signature": {
      "main": [
        [
          {
            "node": "Only quote.approved",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Only quote.approved": {
      "main": [
        [
          {
            "node": "Build contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build contact": {
      "main": [
        [
          {
            "node": "Upsert HighLevel contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
