{
  "name": "Light AI quote approved → HubSpot",
  "nodes": [
    {
      "parameters": {
        "content": "## Quote approved → HubSpot\n1. Activate, copy the production webhook URL and subscribe it in Light AI to quote.approved.\n2. Paste the endpoint secret (whsec_…) into \"Verify signature\".\n3. Pick a HubSpot private app token credential and a \"Light AI API key\" Header Auth credential (a key with projects:write; the endpoint needs clients:read for names).\nDocs: https://docs.light-ai.app/integrations/hubspot",
        "height": 260,
        "width": 380
      },
      "name": "About",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -40,
        -320
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "light-ai-hubspot",
        "responseMode": "onReceived",
        "options": {
          "rawBody": true
        }
      },
      "name": "Light AI webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "webhookId": "light-ai-hubspot"
    },
    {
      "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 !== 'hubspot';\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';\nreturn [{ json: { project_id: quote.project.id, deal: { properties: {\n  dealname: `${name} – lighting${address ? ' – ' + address : ''}`,\n  amount: quote.totals.total.decimal,\n  dealstage: 'closedwon',\n  pipeline: 'default',\n  closedate: quote.approved_at ?? new Date().toISOString(),\n  description: `Light AI quote ${quote.id}${quote.links?.app_url ? ' – ' + quote.links.app_url : ''}`,\n} } } }];"
      },
      "name": "Build deal",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        0
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/deals",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotAppToken",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.deal) }}",
        "options": {}
      },
      "name": "Create HubSpot deal",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        880,
        0
      ]
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "=https://api.light-ai.app/v1/projects/{{ $('Build deal').first().json.project_id }}/external_refs/hubspot",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": false,
        "headerParameters": {
          "parameters": []
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ id: String($json.id) }) }}",
        "options": {}
      },
      "name": "Save deal id in Light AI",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        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 deal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build deal": {
      "main": [
        [
          {
            "node": "Create HubSpot deal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create HubSpot deal": {
      "main": [
        [
          {
            "node": "Save deal id in Light AI",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  }
}
