{
  "name": "Capture website chatbot leads in HubSpot with consent tracking using OpenAI",
  "nodes": [
    {
      "parameters": {
        "content": "## Capture website chatbot leads in HubSpot with consent tracking using OpenAI\n\nFor marketing teams that want chatbot conversations to create HubSpot contacts without adding people to marketing lists by accident.\n\n### How it works\n1. A webhook receives the widget message and checks a shared secret.\n2. An OpenAI agent answers from your knowledge text and asks for an email only when there is a reason.\n3. The capture lead tool creates or updates the contact with chat properties such as interest, intent and summary.\n4. Only after a separate, explicit yes does a second call add the contact to your static list and store the consent wording and time.\n\n### Setup\n- [ ] Edit **Chatbot settings**: company name, your knowledge text and a random `webhook_secret`.\n- [ ] Add your OpenAI credential to **GPT-4.1 mini model**.\n- [ ] Copy each tool section below the main flow into its own new workflow, save it, then select it in the matching tool node.\n- [ ] In each tool workflow, add your HubSpot credential and fill in **Tool settings**.\n- [ ] Run the setup section once to create the custom contact properties, and create a static list in HubSpot.\n- [ ] Test with a POST to the webhook Test URL (the n8n chat panel needs a Chat Trigger). Then activate and add the production URL to your widget, with the same secret as `metadata.webhook_secret`.\n\n### Requirements\nn8n, an OpenAI API key and a HubSpot private app token with contacts, contact schema and lists write scopes.\n\n### Customization tips\nChange the property names, the list or the prompt wording for consent.",
        "width": 480,
        "height": 1104
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        0
      ],
      "id": "30c9aaac-afac-476b-9236-eca649b84cc1",
      "name": "Template overview"
    },
    {
      "parameters": {
        "content": "## 1. Receive and verify the message\nThe webhook takes the widget message. Requests without your secret are rejected.",
        "width": 944,
        "height": 432,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        0
      ],
      "id": "9957da52-d3ca-47c7-980b-3f6627e4bb7d",
      "name": "Section 1: Receive and verify the message"
    },
    {
      "parameters": {
        "content": "## 2. Answer with the AI agent\nThe agent answers from your settings, remembers the session and calls its tools when needed.",
        "width": 704,
        "height": 512,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        528
      ],
      "id": "77df5c5f-cd3a-4b6c-87e0-0da8109b9999",
      "name": "Section 2: Answer with the AI agent"
    },
    {
      "parameters": {
        "content": "## 3. Tool: capture_lead (copy to its own workflow)\nCopy these nodes into a new workflow and save it. Then select that workflow in the capture_lead tool node.",
        "width": 2592,
        "height": 672,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        1136
      ],
      "id": "4b5dcd55-82c5-4b0a-86fc-e0679600ef12",
      "name": "Section 3: Tool: capture_lead (copy to its own workflow)"
    },
    {
      "parameters": {
        "content": "## 4. Run once: create HubSpot properties (copy to its own workflow)\nCopy these nodes into a new workflow. Run it once with the manual trigger. Safe to run again.",
        "width": 1328,
        "height": 368,
        "color": 7
      },
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        0,
        1904
      ],
      "id": "678696eb-7543-499d-bacf-b42dab1f4deb",
      "name": "Section 4: Run once: create HubSpot properties (copy to its own workflow)"
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "hubspot-lead-capture",
        "responseMode": "responseNode",
        "options": {}
      },
      "id": "webhook",
      "name": "Chat webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        64,
        112
      ],
      "webhookId": "hubspot-lead-capture-webhook"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "c1",
              "name": "company_name",
              "value": "Your Company",
              "type": "string"
            },
            {
              "id": "c4",
              "name": "consent_wording",
              "value": "Want me to add you to our monthly update email as well? One click to unsubscribe any time.",
              "type": "string"
            },
            {
              "id": "c5",
              "name": "knowledge",
              "value": "PASTE YOUR FAQ OR SERVICES COPY HERE.\n\nThe bot answers only from this text.",
              "type": "string"
            },
            {
              "id": "c7",
              "name": "webhook_secret",
              "value": "CHANGE_ME_TO_A_RANDOM_STRING",
              "type": "string"
            },
            {
              "id": "c8",
              "name": "message",
              "value": "={{ $json.body.chatInput || $json.body.message || '' }}",
              "type": "string"
            },
            {
              "id": "c9",
              "name": "session_id",
              "value": "={{ $json.body.sessionId || 'no-session' }}",
              "type": "string"
            },
            {
              "id": "c10",
              "name": "page_url",
              "value": "={{ $json.body.metadata?.page_url || '' }}",
              "type": "string"
            },
            {
              "id": "c11",
              "name": "sent_secret",
              "value": "={{ $json.body.metadata?.webhook_secret || (typeof $json.body.metadata === 'string' ? ($json.body.metadata.match(/\"webhook_secret\"\\s*:\\s*\"([^\"]*)\"/) || [])[1] : '') || '' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "config",
      "name": "Chatbot settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        304,
        112
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "sec1",
              "leftValue": "={{ $json.sent_secret }}",
              "rightValue": "={{ $json.webhook_secret }}",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "check-secret",
      "name": "Request from your widget?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        544,
        112
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ output: 'Sorry, something went wrong. Please try again later.' }) }}",
        "options": {
          "responseCode": 401
        }
      },
      "id": "reject",
      "name": "Reject unknown requests",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        784,
        272
      ]
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "={{ $json.message }}",
        "options": {
          "systemMessage": "=You are the assistant on {{ $json.company_name }}'s website. Answer visitor questions accurately and, where appropriate, capture their details so a human can follow up.\n\nWHAT YOU KNOW\n{{ $json.knowledge }}\n\nANSWERING\n- Answer only from the information above. If it is not there, say you are not sure.\n- Never invent pricing, availability, or capability.\n\nCAPTURING DETAILS\n- Do not ask for an email in your first reply.\n- Ask only when there is a reason: they cannot be answered, want a demo, or ask to be contacted.\n\nCONSENT - follow this exactly\n- Taking an email to answer a question is one thing. Adding them to a marketing list is separate and needs its own explicit yes.\n- When you have an email and a reason to save it, call capture_lead with consent_given false.\n- Only afterwards, if warranted, ask separately using this wording: \"{{ $json.consent_wording }}\"\n- If and only if they clearly say yes, call capture_lead again with consent_given true and consent_wording set to exactly what you asked.\n- Ambiguity is NOT consent. Never bundle the two asks.\n\nFIELDS - choose only from these values\n- interest: pricing, services, integrations, demo, support, partnership, careers\n- intent: hot, warm, browsing\n- objection: price, timing, competitor, technical-fit, authority\n\nAlways pass session_id \"{{ $json.session_id }}\" and page_url \"{{ $json.page_url }}\".\n\nTONE\n- Short. Plain. No exclamation marks."
        }
      },
      "id": "agent",
      "name": "Answer questions and capture leads",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [
        64,
        640
      ]
    },
    {
      "parameters": {
        "model": "gpt-4.1-mini",
        "options": {}
      },
      "id": "model",
      "name": "GPT-4.1 mini model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        64,
        880
      ]
    },
    {
      "parameters": {
        "sessionIdType": "customKey",
        "sessionKey": "={{ $('Chatbot settings').item.json.session_id }}",
        "contextWindowLength": 20
      },
      "id": "memory",
      "name": "Conversation memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "typeVersion": 1.3,
      "position": [
        304,
        880
      ]
    },
    {
      "parameters": {
        "name": "capture_lead",
        "description": "Save a lead to HubSpot. Call with consent_given false when you first get an email. Call a second time with consent_given true ONLY after a separate explicit yes to marketing.",
        "workflowId": {
          "__rl": true,
          "mode": "list",
          "value": "PASTE_THE_CAPTURE_LEAD_HUBSPOT_WORKFLOW_ID_HERE",
          "cachedResultName": "capture_lead (HubSpot)"
        },
        "specifyInputSchema": true,
        "jsonSchemaExample": "{\n  \"email\": \"sam@example.com\",\n  \"first_name\": \"Sam\",\n  \"last_name\": \"\",\n  \"company\": \"\",\n  \"consent_given\": false,\n  \"consent_wording\": \"\",\n  \"interest\": \"services\",\n  \"intent\": \"hot\",\n  \"objection\": \"\",\n  \"summary\": \"Asked about X. Wants a callback.\",\n  \"session_id\": \"\",\n  \"page_url\": \"\"\n}"
      },
      "id": "tool-capture",
      "name": "capture_lead",
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 1.2,
      "position": [
        544,
        880
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ output: $json.output }) }}",
        "options": {}
      },
      "id": "respond",
      "name": "Respond to widget",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        544,
        640
      ]
    },
    {
      "parameters": {
        "inputSource": "passthrough"
      },
      "id": "sub-trigger",
      "name": "When called by the agent",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        64,
        1488
      ]
    },
    {
      "parameters": {
        "jsCode": "const q = $input.first().json.query ?? $input.first().json;\nconst d = typeof q === 'string' ? JSON.parse(q) : q;\n\nconst email = String(d.email || '').trim().toLowerCase();\nconst valid = /^[^@\\s]+@[^@\\s]+\\.[^@\\s]{2,}$/.test(email);\n\nreturn [{ json: {\n  valid,\n  email,\n  first_name: d.first_name || '',\n  last_name: d.last_name || '',\n  company: d.company || '',\n  consent_given: d.consent_given === true || d.consent_given === 'true',\n  consent_wording: d.consent_wording || '',\n  interest: ['pricing','services','integrations','demo','support','partnership','careers'].includes(d.interest) ? d.interest : '',\n  intent: ['hot','warm','browsing'].includes(d.intent) ? d.intent : 'browsing',\n  objection: ['price','timing','competitor','technical-fit','authority'].includes(d.objection) ? d.objection : '',\n  summary: d.summary || '',\n  session_id: d.session_id || '',\n  page_url: d.page_url || ''\n} }];"
      },
      "id": "parse-input",
      "name": "Parse and validate",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        284,
        1488
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "v1",
              "leftValue": "={{ $json.valid }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-valid",
      "name": "Valid email?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        504,
        1488
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "e1",
              "name": "response",
              "value": "That email address doesn't look valid. Ask them to confirm it, then try again. Nothing has been saved.",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "invalid-response",
      "name": "Tell the agent it failed",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        724,
        1648
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "list_id",
              "value": "1",
              "type": "string"
            }
          ]
        },
        "includeOtherFields": true,
        "options": {}
      },
      "id": "sub-config",
      "name": "Tool settings",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        724,
        1348
      ]
    },
    {
      "parameters": {
        "jsCode": "// Build the properties object once, reused by both the update attempt and the create fallback.\nconst d = $input.first().json;\n\nconst properties = {\n  email: d.email,\n  firstname: d.first_name,\n  lastname: d.last_name,\n  company: d.company,\n  chat_session_id: d.session_id,\n  chat_first_seen_url: d.page_url,\n  chat_last_summary: d.summary,\n  chat_interest: d.interest,\n  chat_intent: d.intent,\n  chat_objection: d.objection\n};\n\nif (d.consent_given) {\n  properties.chat_consent_timestamp = new Date().toISOString();\n  properties.chat_consent_wording = d.consent_wording;\n}\n\nreturn [{ json: { ...d, properties } }];"
      },
      "id": "build-properties",
      "name": "Build properties object",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        884,
        1348
      ]
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "={{ 'https://api.hubapi.com/crm/v3/objects/contacts/' + encodeURIComponent($json.email) + '?idProperty=email' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: $json.properties }) }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        }
      },
      "id": "try-update-contact",
      "name": "Try update contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1044,
        1348
      ]
    },
    {
      "parameters": {
        "jsCode": "// PATCH by idProperty=email only updates an EXISTING contact - HubSpot has\n// no single-object upsert, so a 404 here means we need to create instead.\n// Confirmed against HubSpot's own docs: this endpoint does not create on miss.\nconst full = $input.first().json;\nconst statusCode = full.statusCode || 200;\n\nreturn [{ json: {\n  ...$('Build properties object').first().json,\n  update_succeeded: statusCode >= 200 && statusCode < 300,\n  update_result: full.body || full\n} }];"
      },
      "id": "check-update-result",
      "name": "Did the update succeed?",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1204,
        1348
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "u1",
              "leftValue": "={{ $json.update_succeeded }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-update-succeeded",
      "name": "Contact already existed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1364,
        1348
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/contacts",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: $json.properties }) }}",
        "options": {
          "response": {
            "response": {
              "neverError": true,
              "fullResponse": true
            }
          }
        }
      },
      "id": "create-contact",
      "name": "Create new contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1544,
        1468
      ]
    },
    {
      "parameters": {
        "jsCode": "// Whichever branch ran (update or create), pull out the contact id from\n// whatever HubSpot returned and normalise into one shape for downstream nodes.\nconst prior = $('Did the update succeed?').first().json;\nconst wasUpdate = prior.update_succeeded;\n\nlet contact;\nif (wasUpdate) {\n  contact = prior.update_result;\n} else {\n  const created = $input.first().json;\n  contact = created.body || created;\n}\n\nconst contactId = contact?.id;\n\nif (!contactId) {\n  throw new Error('HubSpot did not return a contact id from either the update or create call. Check the private app token, its scopes, and that the custom properties from the setup workflow exist.');\n}\n\nreturn [{ json: { ...prior, contact_id: contactId } }];"
      },
      "id": "extract-contact-id",
      "name": "Extract contact ID",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1764,
        1348
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "cg1",
              "leftValue": "={{ $('Tool settings').first().json.consent_given }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "consent-gate",
      "name": "CONSENT GATE",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1984,
        1348
      ]
    },
    {
      "parameters": {
        "method": "PUT",
        "url": "={{ 'https://api.hubapi.com/crm/v3/lists/' + $json.list_id + '/memberships/add' }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify([ $json.contact_id ]) }}",
        "options": {}
      },
      "id": "add-to-list",
      "name": "Add to list",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2204,
        1248
      ]
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "r1",
              "name": "response",
              "value": "={{ $('Tool settings').first().json.consent_given ? 'Saved. Contact created or updated and subscribed to the marketing list with consent recorded.' : 'Saved. Contact created or updated. NOT subscribed to any marketing list.' }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "id": "success-response",
      "name": "Tell the agent it worked",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2424,
        1348
      ]
    },
    {
      "parameters": {},
      "id": "setup-trigger",
      "name": "Click to run setup",
      "type": "n8n-nodes-base.manualTrigger",
      "typeVersion": 1,
      "position": [
        64,
        2116
      ]
    },
    {
      "parameters": {
        "url": "https://api.hubapi.com/crm/v3/properties/contacts",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "id": "get-properties",
      "name": "Get existing properties",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        284,
        2116
      ]
    },
    {
      "parameters": {
        "jsCode": "const existing = ($input.first().json.results || []).map(p => p.name);\n\nconst wanted = [\n  { name: 'chat_session_id', label: 'Chat Session ID', type: 'string', fieldType: 'text' },\n  { name: 'chat_first_seen_url', label: 'Chat First Seen URL', type: 'string', fieldType: 'text' },\n  { name: 'chat_last_summary', label: 'Chat Last Summary', type: 'string', fieldType: 'textarea' },\n  { name: 'chat_consent_timestamp', label: 'Chat Consent Timestamp', type: 'string', fieldType: 'text' },\n  { name: 'chat_consent_wording', label: 'Chat Consent Wording', type: 'string', fieldType: 'textarea' },\n  {\n    name: 'chat_interest', label: 'Chat Interest', type: 'enumeration', fieldType: 'select',\n    options: ['pricing','services','integrations','demo','support','partnership','careers']\n  },\n  {\n    name: 'chat_intent', label: 'Chat Intent', type: 'enumeration', fieldType: 'select',\n    options: ['hot','warm','browsing']\n  },\n  {\n    name: 'chat_objection', label: 'Chat Objection', type: 'enumeration', fieldType: 'select',\n    options: ['price','timing','competitor','technical-fit','authority']\n  }\n];\n\nconst missing = wanted.filter(f => !existing.includes(f.name));\n\nif (missing.length === 0) {\n  return [{ json: { skip: true, message: 'All properties already exist.' } }];\n}\n\nreturn missing.map(f => {\n  const body = {\n    name: f.name,\n    label: f.label,\n    type: f.type,\n    fieldType: f.fieldType,\n    groupName: 'contactinformation'\n  };\n  if (f.options) {\n    body.options = f.options.map((o, i) => ({ label: o, value: o, displayOrder: i }));\n  }\n  return { json: { skip: false, body } };\n});"
      },
      "id": "plan-properties",
      "name": "Which properties are missing?",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        504,
        2116
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "loose",
            "version": 2
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{ $json.skip }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-properties-needed",
      "name": "Any properties to create?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        724,
        2116
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/properties/contacts",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.body) }}",
        "options": {}
      },
      "id": "create-properties",
      "name": "Create properties",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        944,
        2016
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{ json: {\n  summary: 'Setup complete. Custom properties are in place.\\n\\nNext: create a static list in HubSpot for opted-in contacts (Contacts -> Lists), note its list ID, then open the main workflow and fill in CONFIG.'\n} }];"
      },
      "id": "summarise",
      "name": "Setup summary",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1164,
        2116
      ]
    }
  ],
  "pinData": {},
  "connections": {
    "Chat webhook": {
      "main": [
        [
          {
            "node": "Chatbot settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chatbot settings": {
      "main": [
        [
          {
            "node": "Request from your widget?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request from your widget?": {
      "main": [
        [
          {
            "node": "Answer questions and capture leads",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Reject unknown requests",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Answer questions and capture leads": {
      "main": [
        [
          {
            "node": "Respond to widget",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT-4.1 mini model": {
      "ai_languageModel": [
        [
          {
            "node": "Answer questions and capture leads",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Conversation memory": {
      "ai_memory": [
        [
          {
            "node": "Answer questions and capture leads",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "capture_lead": {
      "ai_tool": [
        [
          {
            "node": "Answer questions and capture leads",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "When called by the agent": {
      "main": [
        [
          {
            "node": "Parse and validate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse and validate": {
      "main": [
        [
          {
            "node": "Valid email?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Valid email?": {
      "main": [
        [
          {
            "node": "Tool settings",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Tell the agent it failed",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tool settings": {
      "main": [
        [
          {
            "node": "Build properties object",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build properties object": {
      "main": [
        [
          {
            "node": "Try update contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Try update contact": {
      "main": [
        [
          {
            "node": "Did the update succeed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Did the update succeed?": {
      "main": [
        [
          {
            "node": "Contact already existed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Contact already existed?": {
      "main": [
        [
          {
            "node": "Extract contact ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create new contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create new contact": {
      "main": [
        [
          {
            "node": "Extract contact ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract contact ID": {
      "main": [
        [
          {
            "node": "CONSENT GATE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CONSENT GATE": {
      "main": [
        [
          {
            "node": "Add to list",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Tell the agent it worked",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Add to list": {
      "main": [
        [
          {
            "node": "Tell the agent it worked",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Click to run setup": {
      "main": [
        [
          {
            "node": "Get existing properties",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get existing properties": {
      "main": [
        [
          {
            "node": "Which properties are missing?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Which properties are missing?": {
      "main": [
        [
          {
            "node": "Any properties to create?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Any properties to create?": {
      "main": [
        [
          {
            "node": "Create properties",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Setup summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create properties": {
      "main": [
        [
          {
            "node": "Setup summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": []
}