{
  "updatedAt": "2026-05-25T15:39:54.908Z",
  "createdAt": "2026-05-22T16:57:37.438Z",
  "id": "DycMMqpsFubSr8ar",
  "name": "[QUO] Bidirectional Contact Sync",
  "description": null,
  "active": true,
  "isArchived": false,
  "nodes": [
    {
      "id": "quo-webhook",
      "name": "Quo Call Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        0
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "quo-call-sync",
        "responseMode": "responseNode",
        "options": {}
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "respond-200",
      "name": "Respond 200",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        240,
        0
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ received: true }) }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "parse-quo",
      "name": "Parse Quo Payload",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        0
      ],
      "parameters": {
        "jsCode": "// Purpose: Extract call data from Quo API v3 webhook\n// Inputs: Quo webhook body with data.object containing from/to/direction\n// Outputs: phone (contact's number), callId, direction\n\nconst body = $input.first().json.body || $input.first().json;\nconst callObj = (body.data && body.data.object) || {};\n\nconst callId = callObj.id || null;\nconst direction = callObj.direction || '';\nconst from = callObj.from || null;\nconst to = callObj.to || null;\n\n// Contact's phone: outgoing = we called them (to), incoming = they called us (from)\nconst phone = direction === 'outgoing' ? to : from;\n\nreturn [{ json: { phone, callId, direction } }];"
      }
    },
    {
      "id": "has-phone",
      "name": "Has Phone?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        720,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-phone",
              "leftValue": "={{ $json.phone }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "normalize-phone",
      "name": "Normalize Phone",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        0
      ],
      "parameters": {
        "jsCode": "// Purpose: Normalize raw phone from webhook for HubSpot search and formatting\n// Inputs: phone (raw E.164 from Quo webhook)\n// Outputs: phone, phoneNormalized (10 digits), phoneFormatted (+1 (xxx) xxx-xxxx)\n\nconst raw = $json.phone || '';\nconst digits = raw.replace(/[^0-9]/g, '');\nconst phoneNormalized = (digits.length === 11 && digits.startsWith('1')) ? digits.slice(1) : digits;\nconst phoneFormatted = phoneNormalized.length === 10\n  ? '+1 (' + phoneNormalized.slice(0,3) + ') ' + phoneNormalized.slice(3,6) + '-' + phoneNormalized.slice(6)\n  : raw;\n\nreturn [{ json: { phone: raw, phoneNormalized, phoneFormatted } }];"
      }
    },
    {
      "id": "fetch-quo-contact",
      "name": "Fetch Quo Contact",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        0
      ],
      "parameters": {
        "jsCode": "// Purpose: Find Quo contact by phone via pagination (filter doesn't work)\n// Inputs: Normalize Phone (raw phone + 10-digit base)\n// Outputs: { data: [matched contact] } matching Quo API response shape\n\nconst normData = $('Normalize Phone').first().json;\nconst searchBase = normData.phoneNormalized || '';\n\nconst toBase = (p) => {\n  const d = (p || '').replace(/[^0-9]/g, '');\n  return (d.length === 11 && d.startsWith('1')) ? d.slice(1) : d;\n};\n\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\nconst MAX_PAGES = 60;\n\nlet pageToken = null;\nlet matched = null;\nlet pagesScanned = 0;\nlet totalScanned = 0;\nlet lastError = null;\n\nfor (let i = 0; i < MAX_PAGES; i++) {\n  pagesScanned++;\n  let url = 'https://api.openphone.com/v1/contacts?maxResults=50';\n  if (pageToken) url += '&pageToken=' + encodeURIComponent(pageToken);\n\n  let response;\n  try {\n    response = await this.helpers.httpRequest({\n      method: 'GET',\n      url: url,\n      headers: { 'Authorization': QUO_API_KEY },\n      json: true\n    });\n  } catch (err) {\n    lastError = (err && err.message) || String(err);\n    break;\n  }\n\n  const contacts = Array.isArray(response && response.data) ? response.data : [];\n  totalScanned += contacts.length;\n\n  for (const c of contacts) {\n    const phones = Array.isArray(c && c.defaultFields && c.defaultFields.phoneNumbers) ? c.defaultFields.phoneNumbers : [];\n    if (phones.some(p => toBase(p.value) === searchBase)) {\n      matched = c;\n      break;\n    }\n  }\n  if (matched) break;\n\n  pageToken = response && response.nextPageToken;\n  if (!pageToken) break;\n}\n\nreturn [{ json: { data: matched ? [matched] : [], pagesScanned, totalScanned, matched: !!matched, lastError } }];"
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "normalize-contact",
      "name": "Normalize Contact Data",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        0
      ],
      "parameters": {
        "jsCode": "// Purpose: Extract fields from matched Quo contact\n// Inputs: Fetch Quo Contact (data: [matched]), Normalize Phone\n// Outputs: firstName, lastName, company, phone, phoneNormalized, phoneFormatted\n\nconst quoResult = $input.first().json;\nconst normData = $('Normalize Phone').first().json;\nconst rawPhone = normData.phone || '';\nconst searchBase = normData.phoneNormalized || '';\n\nconst allContacts = Array.isArray(quoResult.data) ? quoResult.data : [];\nconst matched = allContacts[0] || null;\n\nconst fields = matched ? (matched.defaultFields || {}) : {};\nconst firstName = fields.firstName || '';\nconst lastName = fields.lastName || '';\nconst company = fields.company || '';\n\nreturn [{ json: { firstName, lastName, company, phone: rawPhone, phoneNormalized: searchBase, phoneFormatted: normData.phoneFormatted } }];"
      }
    },
    {
      "id": "search-hs-phone",
      "name": "Search HS by Phone",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1680,
        0
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/contacts/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'phone', operator: 'EQ', value: $json.phoneNormalized }] }, { filters: [{ propertyName: 'phone', operator: 'EQ', value: '+1' + $json.phoneNormalized }] }, { filters: [{ propertyName: 'phone', operator: 'EQ', value: $json.phoneFormatted }] }, { filters: [{ propertyName: 'mobilephone', operator: 'EQ', value: $json.phoneNormalized }] }, { filters: [{ propertyName: 'mobilephone', operator: 'EQ', value: '+1' + $json.phoneNormalized }] }], properties: ['firstname','lastname','phone','mobilephone'], limit: 1 }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "contact-exists",
      "name": "Contact Exists?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1920,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-total",
              "leftValue": "={{ $json.total }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "update-hs-contact",
      "name": "Update HS Contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2160,
        -140
      ],
      "parameters": {
        "method": "PATCH",
        "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.results[0].id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: { phone: $('Normalize Phone').first().json.phoneFormatted } }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "set-contact-id",
      "name": "Set Contact ID",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        2400,
        -140
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "hsContactId",
              "value": "={{ $json.id }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "has-company",
      "name": "Has Company?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2640,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-company",
              "leftValue": "={{ $('Normalize Contact Data').first().json.company }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "search-hs-company",
      "name": "Search HS Company",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2880,
        -100
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/companies/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'name', operator: 'EQ', value: $('Normalize Contact Data').first().json.company }] }], properties: ['name'], limit: 1 }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "company-exists",
      "name": "Company Exists?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        3120,
        -100
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-company-total",
              "leftValue": "={{ $json.total }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "set-company-id",
      "name": "Set Company ID",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3360,
        -220
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "hsCompanyId",
              "value": "={{ $json.results[0].id }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "create-hs-company",
      "name": "Create HS Company",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3360,
        20
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/companies",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: { name: $('Normalize Contact Data').first().json.company } }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "set-new-company-id",
      "name": "Set New Company ID",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3600,
        20
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "hsCompanyId",
              "value": "={{ $json.id }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "get-all-ids",
      "name": "Get All IDs",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3840,
        -100
      ],
      "parameters": {
        "jsCode": "// Purpose: Collect hsContactId + hsCompanyId for association\n// Inputs: Set Company ID or Set New Company ID, plus either Set Contact ID or Set New Contact ID\n// Outputs: hsContactId, hsCompanyId\n\nconst hsCompanyId = $input.first().json.hsCompanyId;\n\nlet hsContactId = null;\ntry { const v = $('Set Contact ID').first().json.hsContactId; if (v) hsContactId = v; } catch(e) {}\nif (!hsContactId) {\n  try { const v = $('Set New Contact ID').first().json.hsContactId; if (v) hsContactId = v; } catch(e) {}\n}\n\nif (!hsContactId || !hsCompanyId) {\n  throw new Error('Cannot associate - missing IDs. contactId=' + hsContactId + ', companyId=' + hsCompanyId);\n}\n\nreturn [{ json: { hsContactId, hsCompanyId } }];"
      }
    },
    {
      "id": "associate",
      "name": "Associate Contact + Company",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        4080,
        -100
      ],
      "parameters": {
        "method": "PUT",
        "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.hsContactId }}/associations/companies/{{ $json.hsCompanyId }}/contact_to_company",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "quo-contact-webhook",
      "name": "Quo Contact Updated Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        700
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "quo-contact-updated",
        "responseMode": "responseNode",
        "options": {}
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "respond-200-c2",
      "name": "Respond 200 (Contact)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        240,
        700
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ received: true }) }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "parse-quo-contact",
      "name": "Parse Quo Contact Update",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        700
      ],
      "parameters": {
        "jsCode": "// Purpose: Extract contact fields from Quo contact.updated webhook\n// Inputs: Quo webhook body \u2014 firstName/lastName/company directly on data.object, phone at data.object.fields.Phone\n// Outputs: firstName, lastName, company, phone, email, quoContactId\n\nconst body = $input.first().json.body || $input.first().json;\nconst obj = (body.data && body.data.object) || {};\n\nconst firstName = obj.firstName || '';\nconst lastName = obj.lastName || '';\nconst company = obj.company || '';\nconst phone = (obj.fields && obj.fields.Phone) || '';\nconst email = (obj.fields && obj.fields.Email) || '';\nconst quoContactId = obj.id || '';\n\nreturn [{ json: { firstName, lastName, company, phone, email, quoContactId } }];"
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "has-phone-c2",
      "name": "Has Phone? (Contact)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        720,
        700
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-phone-c2",
              "leftValue": "={{ $json.phone }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "normalize-phone-c2",
      "name": "Normalize Phone (Contact)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        960,
        700
      ],
      "parameters": {
        "jsCode": "// Purpose: Normalize raw phone from Quo contact for HubSpot search\n// Inputs: phone (raw E.164), all Quo contact fields in $json\n// Outputs: all Quo fields carried forward, phoneNormalized (10 digits), phoneFormatted\n\nconst raw = $json.phone || '';\nconst digits = raw.replace(/[^0-9]/g, '');\nconst phoneNormalized = (digits.length === 11 && digits.startsWith('1')) ? digits.slice(1) : digits;\nconst phoneFormatted = phoneNormalized.length === 10\n  ? '+1 (' + phoneNormalized.slice(0,3) + ') ' + phoneNormalized.slice(3,6) + '-' + phoneNormalized.slice(6)\n  : raw;\n\nreturn [{ json: { ...$json, phoneNormalized, phoneFormatted } }];"
      }
    },
    {
      "id": "search-hs-phone-c2",
      "name": "Search HS by Phone (Contact)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1200,
        700
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/contacts/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'phone', operator: 'EQ', value: $json.phoneNormalized }] }, { filters: [{ propertyName: 'phone', operator: 'EQ', value: '+1' + $json.phoneNormalized }] }, { filters: [{ propertyName: 'phone', operator: 'EQ', value: $json.phoneFormatted }] }, { filters: [{ propertyName: 'mobilephone', operator: 'EQ', value: $json.phoneNormalized }] }, { filters: [{ propertyName: 'mobilephone', operator: 'EQ', value: '+1' + $json.phoneNormalized }] }], properties: ['firstname','lastname','company','phone','mobilephone','email'], limit: 1 }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "hs-found-c2",
      "name": "HS Contact Found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1440,
        700
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-hs-total-c2",
              "leftValue": "={{ $json.total }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "build-hs-patch",
      "name": "Build HS Patch",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        560
      ],
      "parameters": {
        "jsCode": "// Purpose: Compare Quo contact fields with current HubSpot values; build minimal patch\n// Inputs: HS search result (results[0].properties), Normalize Phone (Contact) for Quo source data\n// Outputs: properties (changed fields only), hsContactId, changeCount\n\nconst hsResult = $input.first().json;\nconst hsContact = (hsResult.results && hsResult.results[0]) || {};\nconst hsProps = hsContact.properties || {};\nconst quoData = $('Normalize Phone (Contact)').first().json;\n\nconst candidates = [\n  ['firstname', quoData.firstName],\n  ['lastname', quoData.lastName],\n  ['company', quoData.company],\n  ['phone', quoData.phoneFormatted],\n  ['email', quoData.email]\n];\n\nconst props = {};\nfor (const [key, val] of candidates) {\n  if (val && (hsProps[key] || '') !== val) props[key] = val;\n}\n\nreturn [{ json: { properties: props, hsContactId: hsContact.id, changeCount: Object.keys(props).length } }];"
      }
    },
    {
      "id": "has-changes-c2",
      "name": "Has Changes? (Quo->HS)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1920,
        560
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-changes-c2",
              "leftValue": "={{ $json.changeCount }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "patch-hs-c2",
      "name": "Patch HS Contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2160,
        560
      ],
      "parameters": {
        "method": "PATCH",
        "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.hsContactId }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: $json.properties }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "hs-contact-webhook",
      "name": "HS Contact Change Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        1300
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "hs-contact-change",
        "responseMode": "responseNode",
        "options": {}
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "respond-200-c3",
      "name": "Respond 200 (HS)",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        240,
        1300
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={{ JSON.stringify({ received: true }) }}",
        "options": {
          "responseCode": 200
        }
      }
    },
    {
      "id": "parse-hs-event",
      "name": "Parse HS Event",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        1300
      ],
      "parameters": {
        "jsCode": "// Purpose: Extract contact ID and changed tracked fields from HubSpot propertyChange webhook\n// Inputs: HubSpot webhook payload (array of change events for one contact)\n// Outputs: objectId, changes (map of propertyName->value), changeCount\n\nconst body = $input.first().json.body || $input.first().json;\nconst events = Array.isArray(body) ? body : [body];\n\nconst trackedProps = new Set(['firstname', 'lastname', 'company', 'phone', 'email']);\nconst changes = {};\nlet objectId = null;\n\nfor (const ev of events) {\n  if (ev.objectId) objectId = String(ev.objectId);\n  if (trackedProps.has(ev.propertyName)) {\n    changes[ev.propertyName] = ev.propertyValue || '';\n  }\n}\n\nconst changeCount = Object.keys(changes).length;\nreturn [{ json: { objectId, changes, changeCount } }];"
      }
    },
    {
      "id": "tracked-prop",
      "name": "Tracked Property?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        720,
        1300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-change-count",
              "leftValue": "={{ $json.changeCount }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "get-hs-full",
      "name": "Get Full HS Contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        960,
        1300
      ],
      "parameters": {
        "method": "GET",
        "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.objectId }}?properties=firstname,lastname,company,phone,mobilephone,email",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "has-phone-c3",
      "name": "Has Phone? (HS)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1200,
        1300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-hs-phone-c3",
              "leftValue": "={{ ($json.properties && ($json.properties.phone || $json.properties.mobilephone)) || '' }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "normalize-phone-c3",
      "name": "Normalize Phone (HS)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        1300
      ],
      "parameters": {
        "jsCode": "// Purpose: Normalize HubSpot contact phone for Quo lookup; carry all sync fields forward\n// Inputs: Get Full HS Contact response\n// Outputs: hsContactId, firstName, lastName, company, email, phone, phoneNormalized, phoneFormatted, changedFields\n\nconst hs = $input.first().json;\nconst props = hs.properties || {};\n\nconst raw = props.phone || props.mobilephone || '';\nconst digits = raw.replace(/[^0-9]/g, '');\nconst phoneNormalized = (digits.length === 11 && digits.startsWith('1')) ? digits.slice(1) : digits;\nconst phoneFormatted = phoneNormalized.length === 10\n  ? '+1 (' + phoneNormalized.slice(0,3) + ') ' + phoneNormalized.slice(3,6) + '-' + phoneNormalized.slice(6)\n  : raw;\n\nreturn [{ json: {\n  hsContactId: hs.id,\n  firstName: props.firstname || '',\n  lastName: props.lastname || '',\n  company: props.company || '',\n  email: props.email || '',\n  phone: raw,\n  phoneNormalized,\n  phoneFormatted,\n  changedFields: $('Parse HS Event').first().json.changes\n} }];"
      }
    },
    {
      "id": "fetch-quo-c3",
      "name": "Fetch Quo Contact (HS Change)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1680,
        1300
      ],
      "parameters": {
        "jsCode": "// Purpose: Find Quo contact by phone via pagination for HS->Quo sync\n// Inputs: Normalize Phone (HS) with phoneNormalized and all contact fields\n// Outputs: all HS fields carried forward, quoContact object, matchCount\n\nconst normData = $input.first().json;\nconst searchBase = normData.phoneNormalized || '';\n\nconst toBase = (p) => {\n  const d = (p || '').replace(/[^0-9]/g, '');\n  return (d.length === 11 && d.startsWith('1')) ? d.slice(1) : d;\n};\n\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\nconst MAX_PAGES = 60;\nlet pageToken = null;\nlet matched = null;\n\nfor (let i = 0; i < MAX_PAGES; i++) {\n  let url = 'https://api.openphone.com/v1/contacts?maxResults=50';\n  if (pageToken) url += '&pageToken=' + encodeURIComponent(pageToken);\n  let response;\n  try {\n    response = await this.helpers.httpRequest({\n      method: 'GET', url,\n      headers: { 'Authorization': QUO_API_KEY },\n      json: true\n    });\n  } catch (err) { break; }\n  const contacts = Array.isArray(response && response.data) ? response.data : [];\n  for (const c of contacts) {\n    const phones = Array.isArray(c && c.defaultFields && c.defaultFields.phoneNumbers) ? c.defaultFields.phoneNumbers : [];\n    if (phones.some(p => toBase(p.value) === searchBase)) { matched = c; break; }\n  }\n  if (matched) break;\n  pageToken = response && response.nextPageToken;\n  if (!pageToken) break;\n}\n\nreturn [{ json: { ...normData, quoContact: matched, matchCount: matched ? 1 : 0 } }];"
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "quo-found-c3",
      "name": "Quo Contact Found?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1920,
        1300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-quo-match",
              "leftValue": "={{ $json.matchCount }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "build-quo-patch",
      "name": "Build Quo Patch",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2160,
        1160
      ],
      "parameters": {
        "jsCode": "// Purpose: Compare HS changed fields with current Quo contact; build minimal PUT body\n// Inputs: Fetch Quo Contact (HS Change) with quoContact and changedFields\n// Outputs: quoContactId, putBody (full merged defaultFields), changeCount\n\nconst data = $input.first().json;\nconst quoContact = data.quoContact || {};\nconst quoFields = quoContact.defaultFields || {};\nconst changedFields = data.changedFields || {};\n\nconst toBase = (p) => { const d = (p||'').replace(/[^0-9]/g,''); return (d.length===11&&d.startsWith('1'))?d.slice(1):d; };\nconst quoPhoneBase = toBase(((quoFields.phoneNumbers || [])[0] || {}).value || '');\nconst quoEmailVal = ((quoFields.emails || [])[0] || {}).value || '';\n\nconst mergedFields = { ...quoFields };\nlet changeCount = 0;\n\nif ('firstname' in changedFields && (quoFields.firstName || '') !== changedFields.firstname) {\n  mergedFields.firstName = changedFields.firstname; changeCount++;\n}\nif ('lastname' in changedFields && (quoFields.lastName || '') !== changedFields.lastname) {\n  mergedFields.lastName = changedFields.lastname; changeCount++;\n}\nif ('company' in changedFields && (quoFields.company || '') !== changedFields.company) {\n  mergedFields.company = changedFields.company; changeCount++;\n}\nif ('phone' in changedFields && toBase(changedFields.phone) !== quoPhoneBase) {\n  mergedFields.phoneNumbers = [{ value: changedFields.phone }]; changeCount++;\n}\nif ('email' in changedFields && quoEmailVal !== changedFields.email) {\n  mergedFields.emails = [{ value: changedFields.email }]; changeCount++;\n}\n\nreturn [{ json: { quoContactId: quoContact.id, putBody: { defaultFields: mergedFields }, changeCount } }];"
      }
    },
    {
      "id": "has-changes-c3",
      "name": "Has Changes? (HS->Quo)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2400,
        1160
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-changes-c3",
              "leftValue": "={{ $json.changeCount }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "patch-quo-c3",
      "name": "Patch Quo Contact",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2640,
        1160
      ],
      "parameters": {
        "jsCode": "// Purpose: Update existing Quo contact via OpenPhone API (PUT with merged fields)\n// Inputs: Build Quo Patch with quoContactId and putBody\n// Outputs: success confirmation\n\nconst data = $input.first().json;\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\n\nawait this.helpers.httpRequest({\n  method: 'PUT',\n  url: `https://api.openphone.com/v1/contacts/${data.quoContactId}`,\n  headers: { 'Authorization': QUO_API_KEY, 'Content-Type': 'application/json' },\n  body: JSON.stringify(data.putBody),\n  json: false\n});\n\nreturn [{ json: { success: true, quoContactId: data.quoContactId } }];"
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "create-quo-c3",
      "name": "Create Quo Contact",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2160,
        1440
      ],
      "parameters": {
        "jsCode": "// Purpose: Create new Quo contact from HubSpot data when no Quo match exists\n// Inputs: Fetch Quo Contact (HS Change) with HS field values\n// Outputs: success confirmation\n\nconst data = $input.first().json;\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\n\nconst defaultFields = {};\nif (data.firstName) defaultFields.firstName = data.firstName;\nif (data.lastName) defaultFields.lastName = data.lastName;\nif (data.company) defaultFields.company = data.company;\nif (data.phoneFormatted) defaultFields.phoneNumbers = [{ value: data.phoneFormatted }];\nif (data.email) defaultFields.emails = [{ value: data.email }];\n\nawait this.helpers.httpRequest({\n  method: 'POST',\n  url: 'https://api.openphone.com/v1/contacts',\n  headers: { 'Authorization': QUO_API_KEY, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ defaultFields }),\n  json: false\n});\n\nreturn [{ json: { success: true, created: true } }];"
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "has-company-c2",
      "name": "Has Company? (C2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2400,
        560
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-company-c2",
              "leftValue": "={{ $('Normalize Phone (Contact)').first().json.company }}",
              "rightValue": "",
              "operator": {
                "type": "string",
                "operation": "notEmpty",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "search-hs-company-c2",
      "name": "Search HS Company (C2)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2640,
        420
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/companies/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'name', operator: 'EQ', value: $('Normalize Phone (Contact)').first().json.company }] }], properties: ['name'], limit: 1 }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "company-exists-c2",
      "name": "Company Exists? (C2)",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2880,
        420
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-company-total-c2",
              "leftValue": "={{ $json.total }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "gt"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "set-company-id-c2",
      "name": "Set Company ID (C2)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3120,
        300
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "hsCompanyId",
              "value": "={{ $json.results[0].id }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "create-hs-company-c2",
      "name": "Create HS Company (C2)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3120,
        540
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/companies",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: { name: $('Normalize Phone (Contact)').first().json.company } }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "set-new-company-id-c2",
      "name": "Set New Company ID (C2)",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3360,
        540
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "hsCompanyId",
              "value": "={{ $json.id }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "get-all-ids-c2",
      "name": "Get All IDs (C2)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3600,
        420
      ],
      "parameters": {
        "jsCode": "// Purpose: Collect hsContactId + hsCompanyId for association in Chain 2\n// Inputs: Set Company ID (C2) or Set New Company ID (C2); Build HS Patch for contact ID\n// Outputs: hsContactId, hsCompanyId\n\nconst hsCompanyId = $input.first().json.hsCompanyId;\nconst hsContactId = $('Build HS Patch').first().json.hsContactId;\n\nif (!hsContactId || !hsCompanyId) {\n  throw new Error('Cannot associate - missing IDs. contactId=' + hsContactId + ', companyId=' + hsCompanyId);\n}\n\nreturn [{ json: { hsContactId, hsCompanyId } }];"
      }
    },
    {
      "id": "associate-c2",
      "name": "Associate Contact + Company (C2)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3840,
        420
      ],
      "parameters": {
        "method": "PUT",
        "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.hsContactId }}/associations/companies/{{ $json.hsCompanyId }}/contact_to_company",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "get-old-assoc-c2",
      "name": "Get Old Company Associations",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2500,
        700
      ],
      "parameters": {
        "method": "GET",
        "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $('Build HS Patch').first().json.hsContactId }}/associations/companies",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "remove-old-assoc-c2",
      "name": "Remove Old Company Associations",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2780,
        700
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v4/associations/contacts/companies/batch/archive",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ inputs: ($json.results || []).map(r => ({ from: { id: String($('Build HS Patch').first().json.hsContactId) }, to: [{ id: String(r.id) }] })) }) }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "fetch-call-details",
      "name": "Fetch Call Details",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2160,
        280
      ],
      "parameters": {
        "jsCode": "// Purpose: Fetch call summary/transcript from OpenPhone for AI prospect classification\n// Inputs: Parse Quo Payload (callId)\n// Outputs: callId, summary, hasSummary\n\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\nconst callId = $('Parse Quo Payload').first().json.callId;\n\nif (!callId) return [{ json: { callId: null, summary: '', hasSummary: false } }];\n\nlet text = '';\ntry {\n  const response = await this.helpers.httpRequest({\n    method: 'GET',\n    url: `https://api.openphone.com/v1/calls/${callId}`,\n    headers: { 'Authorization': QUO_API_KEY },\n    json: true\n  });\n  const data = response.data || {};\n  const summary = data.summary || '';\n  const dialogue = (data.transcription && Array.isArray(data.transcription.dialogue))\n    ? data.transcription.dialogue.map(d => `${d.speaker}: ${d.text}`).join(' ')\n    : '';\n  text = summary || dialogue;\n} catch (err) {}\n\nreturn [{ json: { callId, summary: text, hasSummary: !!text } }];"
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "has-summary",
      "name": "Has Call Summary?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2400,
        280
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-summary",
              "leftValue": "={{ $json.hasSummary }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "ai-classify-caller",
      "name": "AI Classify Caller",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2640,
        280
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.openai.com/v1/chat/completions",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "openAiApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'system', content: 'You are a sales qualifier for Renellence, a Canadian medical device company selling to healthcare facilities, clinics, and medical professionals. Analyze the call summary and determine if this caller is a potential business prospect \u2014 a client, healthcare professional, partner, or referral source. Answer YES if there is any business relevance to a medical device company. Answer NO if the caller is clearly unrelated \u2014 e.g. personal call, wrong number, spam, construction worker, delivery driver, or other non-medical tradesperson. Respond with only YES or NO, nothing else.' }, { role: 'user', content: 'Call summary: ' + $json.summary }], max_tokens: 5 }) }}",
        "options": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "is-prospect",
      "name": "Is Prospect?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        2880,
        280
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": false,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "check-prospect",
              "leftValue": "={{ ($json.choices && $json.choices[0] && $json.choices[0].message && $json.choices[0].message.content) || '' }}",
              "rightValue": "YES",
              "operator": {
                "type": "string",
                "operation": "contains"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "create-hs-contact",
      "name": "Create HS Contact",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3120,
        280
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.hubapi.com/crm/v3/objects/contacts",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ (() => { const d = $('Normalize Contact Data').first().json; const props = { phone: d.phoneFormatted }; if (d.firstName) props.firstname = d.firstName; if (d.lastName) props.lastname = d.lastName; return JSON.stringify({ properties: props }); })() }}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "id": "set-new-contact-id",
      "name": "Set New Contact ID",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        3360,
        280
      ],
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "1",
              "name": "hsContactId",
              "value": "={{ $json.id }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Quo Call Webhook": {
      "main": [
        [
          {
            "node": "Respond 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond 200": {
      "main": [
        [
          {
            "node": "Parse Quo Payload",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Quo Payload": {
      "main": [
        [
          {
            "node": "Has Phone?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Phone?": {
      "main": [
        [
          {
            "node": "Normalize Phone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Phone": {
      "main": [
        [
          {
            "node": "Fetch Quo Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Quo Contact": {
      "main": [
        [
          {
            "node": "Normalize Contact Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Contact Data": {
      "main": [
        [
          {
            "node": "Search HS by Phone",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search HS by Phone": {
      "main": [
        [
          {
            "node": "Contact Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Contact Exists?": {
      "main": [
        [
          {
            "node": "Update HS Contact",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Fetch Call Details",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Update HS Contact": {
      "main": [
        [
          {
            "node": "Set Contact ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Contact ID": {
      "main": [
        [
          {
            "node": "Has Company?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Company?": {
      "main": [
        [
          {
            "node": "Search HS Company",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search HS Company": {
      "main": [
        [
          {
            "node": "Company Exists?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Company Exists?": {
      "main": [
        [
          {
            "node": "Set Company ID",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create HS Company",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Company ID": {
      "main": [
        [
          {
            "node": "Get All IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create HS Company": {
      "main": [
        [
          {
            "node": "Set New Company ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set New Company ID": {
      "main": [
        [
          {
            "node": "Get All IDs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get All IDs": {
      "main": [
        [
          {
            "node": "Associate Contact + Company",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Quo Contact Updated Webhook": {
      "main": [
        [
          {
            "node": "Respond 200 (Contact)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond 200 (Contact)": {
      "main": [
        [
          {
            "node": "Parse Quo Contact Update",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Quo Contact Update": {
      "main": [
        [
          {
            "node": "Has Phone? (Contact)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Phone? (Contact)": {
      "main": [
        [
          {
            "node": "Normalize Phone (Contact)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Phone (Contact)": {
      "main": [
        [
          {
            "node": "Search HS by Phone (Contact)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search HS by Phone (Contact)": {
      "main": [
        [
          {
            "node": "HS Contact Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HS Contact Found?": {
      "main": [
        [
          {
            "node": "Build HS Patch",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build HS Patch": {
      "main": [
        [
          {
            "node": "Has Changes? (Quo->HS)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Changes? (Quo->HS)": {
      "main": [
        [
          {
            "node": "Patch HS Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HS Contact Change Webhook": {
      "main": [
        [
          {
            "node": "Respond 200 (HS)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Respond 200 (HS)": {
      "main": [
        [
          {
            "node": "Parse HS Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse HS Event": {
      "main": [
        [
          {
            "node": "Tracked Property?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Tracked Property?": {
      "main": [
        [
          {
            "node": "Get Full HS Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Full HS Contact": {
      "main": [
        [
          {
            "node": "Has Phone? (HS)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Phone? (HS)": {
      "main": [
        [
          {
            "node": "Normalize Phone (HS)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Phone (HS)": {
      "main": [
        [
          {
            "node": "Fetch Quo Contact (HS Change)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Quo Contact (HS Change)": {
      "main": [
        [
          {
            "node": "Quo Contact Found?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Quo Contact Found?": {
      "main": [
        [
          {
            "node": "Build Quo Patch",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create Quo Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Quo Patch": {
      "main": [
        [
          {
            "node": "Has Changes? (HS->Quo)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Changes? (HS->Quo)": {
      "main": [
        [
          {
            "node": "Patch Quo Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Patch HS Contact": {
      "main": [
        [
          {
            "node": "Has Company? (C2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search HS Company (C2)": {
      "main": [
        [
          {
            "node": "Company Exists? (C2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Company Exists? (C2)": {
      "main": [
        [
          {
            "node": "Set Company ID (C2)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Create HS Company (C2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Company ID (C2)": {
      "main": [
        [
          {
            "node": "Get All IDs (C2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create HS Company (C2)": {
      "main": [
        [
          {
            "node": "Set New Company ID (C2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set New Company ID (C2)": {
      "main": [
        [
          {
            "node": "Get All IDs (C2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get All IDs (C2)": {
      "main": [
        [
          {
            "node": "Associate Contact + Company (C2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Company? (C2)": {
      "main": [
        [
          {
            "node": "Get Old Company Associations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Get Old Company Associations": {
      "main": [
        [
          {
            "node": "Remove Old Company Associations",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Remove Old Company Associations": {
      "main": [
        [
          {
            "node": "Search HS Company (C2)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Call Details": {
      "main": [
        [
          {
            "node": "Has Call Summary?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Call Summary?": {
      "main": [
        [
          {
            "node": "AI Classify Caller",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Classify Caller": {
      "main": [
        [
          {
            "node": "Is Prospect?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is Prospect?": {
      "main": [
        [
          {
            "node": "Create HS Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Create HS Contact": {
      "main": [
        [
          {
            "node": "Set New Contact ID",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set New Contact ID": {
      "main": [
        [
          {
            "node": "Has Company?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "callerPolicy": "workflowsFromSameOwner",
    "availableInMCP": false
  },
  "staticData": {
    "node:Poll HS Changes (5 min)": {
      "recurrenceRules": []
    },
    "node:Poll HS Changes (2 min)": {
      "recurrenceRules": []
    }
  },
  "meta": null,
  "versionId": "ebe44fce-336f-4be6-ba1c-06aa97c2e4fe",
  "activeVersionId": "ebe44fce-336f-4be6-ba1c-06aa97c2e4fe",
  "versionCounter": 90,
  "triggerCount": 3,
  "shared": [
    {
      "updatedAt": "2026-05-22T16:57:37.441Z",
      "createdAt": "2026-05-22T16:57:37.441Z",
      "role": "workflow:owner",
      "workflowId": "DycMMqpsFubSr8ar",
      "projectId": "wdxhvXJ3bThm4urt",
      "project": {
        "updatedAt": "2025-10-21T19:34:35.030Z",
        "createdAt": "2025-10-21T19:34:30.889Z",
        "id": "wdxhvXJ3bThm4urt",
        "name": "aria moghimi <aria.moghimi@renellence.com>",
        "type": "personal",
        "icon": null,
        "description": null,
        "projectRelations": [
          {
            "updatedAt": "2025-10-21T19:34:30.890Z",
            "createdAt": "2025-10-21T19:34:30.890Z",
            "userId": "b27d72c1-e755-42d3-8997-420452d0c440",
            "projectId": "wdxhvXJ3bThm4urt",
            "user": {
              "updatedAt": "2026-05-25T13:24:16.000Z",
              "createdAt": "2025-10-21T19:34:29.243Z",
              "id": "b27d72c1-e755-42d3-8997-420452d0c440",
              "email": "aria.moghimi@renellence.com",
              "firstName": "aria",
              "lastName": "moghimi",
              "personalizationAnswers": null,
              "settings": {
                "userActivated": true,
                "easyAIWorkflowOnboarded": true,
                "firstSuccessfulWorkflowId": "EwD8dnh7qa6UR2ro",
                "userActivatedAt": 1761778204455,
                "npsSurvey": {
                  "waitingForResponse": true,
                  "ignoredCount": 2,
                  "lastShownAt": 1779396935724
                }
              },
              "disabled": false,
              "mfaEnabled": false,
              "lastActiveAt": "2026-05-25",
              "isPending": false
            }
          }
        ]
      }
    }
  ],
  "tags": [],
  "activeVersion": {
    "updatedAt": "2026-05-25T15:39:54.909Z",
    "createdAt": "2026-05-25T15:39:54.909Z",
    "versionId": "ebe44fce-336f-4be6-ba1c-06aa97c2e4fe",
    "workflowId": "DycMMqpsFubSr8ar",
    "nodes": [
      {
        "id": "quo-webhook",
        "name": "Quo Call Webhook",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          0,
          0
        ],
        "parameters": {
          "httpMethod": "POST",
          "path": "quo-call-sync",
          "responseMode": "responseNode",
          "options": {}
        },
        "webhookId": "8bf2e818-55dd-4463-a367-988696c492b3",
        "onError": "continueRegularOutput"
      },
      {
        "id": "respond-200",
        "name": "Respond 200",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.1,
        "position": [
          240,
          0
        ],
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify({ received: true }) }}",
          "options": {
            "responseCode": 200
          }
        }
      },
      {
        "id": "parse-quo",
        "name": "Parse Quo Payload",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          480,
          0
        ],
        "parameters": {
          "jsCode": "// Purpose: Extract call data from Quo API v3 webhook\n// Inputs: Quo webhook body with data.object containing from/to/direction\n// Outputs: phone (contact's number), callId, direction\n\nconst body = $input.first().json.body || $input.first().json;\nconst callObj = (body.data && body.data.object) || {};\n\nconst callId = callObj.id || null;\nconst direction = callObj.direction || '';\nconst from = callObj.from || null;\nconst to = callObj.to || null;\n\n// Contact's phone: outgoing = we called them (to), incoming = they called us (from)\nconst phone = direction === 'outgoing' ? to : from;\n\nreturn [{ json: { phone, callId, direction } }];"
        }
      },
      {
        "id": "has-phone",
        "name": "Has Phone?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          720,
          0
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-phone",
                "leftValue": "={{ $json.phone }}",
                "rightValue": "",
                "operator": {
                  "type": "string",
                  "operation": "notEmpty",
                  "singleValue": true
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "normalize-phone",
        "name": "Normalize Phone",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          960,
          0
        ],
        "parameters": {
          "jsCode": "// Purpose: Normalize raw phone from webhook for HubSpot search and formatting\n// Inputs: phone (raw E.164 from Quo webhook)\n// Outputs: phone, phoneNormalized (10 digits), phoneFormatted (+1 (xxx) xxx-xxxx)\n\nconst raw = $json.phone || '';\nconst digits = raw.replace(/[^0-9]/g, '');\nconst phoneNormalized = (digits.length === 11 && digits.startsWith('1')) ? digits.slice(1) : digits;\nconst phoneFormatted = phoneNormalized.length === 10\n  ? '+1 (' + phoneNormalized.slice(0,3) + ') ' + phoneNormalized.slice(3,6) + '-' + phoneNormalized.slice(6)\n  : raw;\n\nreturn [{ json: { phone: raw, phoneNormalized, phoneFormatted } }];"
        }
      },
      {
        "id": "fetch-quo-contact",
        "name": "Fetch Quo Contact",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1200,
          0
        ],
        "parameters": {
          "jsCode": "// Purpose: Find Quo contact by phone via pagination (filter doesn't work)\n// Inputs: Normalize Phone (raw phone + 10-digit base)\n// Outputs: { data: [matched contact] } matching Quo API response shape\n\nconst normData = $('Normalize Phone').first().json;\nconst searchBase = normData.phoneNormalized || '';\n\nconst toBase = (p) => {\n  const d = (p || '').replace(/[^0-9]/g, '');\n  return (d.length === 11 && d.startsWith('1')) ? d.slice(1) : d;\n};\n\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\nconst MAX_PAGES = 60;\n\nlet pageToken = null;\nlet matched = null;\nlet pagesScanned = 0;\nlet totalScanned = 0;\nlet lastError = null;\n\nfor (let i = 0; i < MAX_PAGES; i++) {\n  pagesScanned++;\n  let url = 'https://api.openphone.com/v1/contacts?maxResults=50';\n  if (pageToken) url += '&pageToken=' + encodeURIComponent(pageToken);\n\n  let response;\n  try {\n    response = await this.helpers.httpRequest({\n      method: 'GET',\n      url: url,\n      headers: { 'Authorization': QUO_API_KEY },\n      json: true\n    });\n  } catch (err) {\n    lastError = (err && err.message) || String(err);\n    break;\n  }\n\n  const contacts = Array.isArray(response && response.data) ? response.data : [];\n  totalScanned += contacts.length;\n\n  for (const c of contacts) {\n    const phones = Array.isArray(c && c.defaultFields && c.defaultFields.phoneNumbers) ? c.defaultFields.phoneNumbers : [];\n    if (phones.some(p => toBase(p.value) === searchBase)) {\n      matched = c;\n      break;\n    }\n  }\n  if (matched) break;\n\n  pageToken = response && response.nextPageToken;\n  if (!pageToken) break;\n}\n\nreturn [{ json: { data: matched ? [matched] : [], pagesScanned, totalScanned, matched: !!matched, lastError } }];"
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "normalize-contact",
        "name": "Normalize Contact Data",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1440,
          0
        ],
        "parameters": {
          "jsCode": "// Purpose: Extract fields from matched Quo contact\n// Inputs: Fetch Quo Contact (data: [matched]), Normalize Phone\n// Outputs: firstName, lastName, company, phone, phoneNormalized, phoneFormatted\n\nconst quoResult = $input.first().json;\nconst normData = $('Normalize Phone').first().json;\nconst rawPhone = normData.phone || '';\nconst searchBase = normData.phoneNormalized || '';\n\nconst allContacts = Array.isArray(quoResult.data) ? quoResult.data : [];\nconst matched = allContacts[0] || null;\n\nconst fields = matched ? (matched.defaultFields || {}) : {};\nconst firstName = fields.firstName || '';\nconst lastName = fields.lastName || '';\nconst company = fields.company || '';\n\nreturn [{ json: { firstName, lastName, company, phone: rawPhone, phoneNormalized: searchBase, phoneFormatted: normData.phoneFormatted } }];"
        }
      },
      {
        "id": "search-hs-phone",
        "name": "Search HS by Phone",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          1680,
          0
        ],
        "parameters": {
          "method": "POST",
          "url": "https://api.hubapi.com/crm/v3/objects/contacts/search",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'phone', operator: 'EQ', value: $json.phoneNormalized }] }, { filters: [{ propertyName: 'phone', operator: 'EQ', value: '+1' + $json.phoneNormalized }] }, { filters: [{ propertyName: 'phone', operator: 'EQ', value: $json.phoneFormatted }] }, { filters: [{ propertyName: 'mobilephone', operator: 'EQ', value: $json.phoneNormalized }] }, { filters: [{ propertyName: 'mobilephone', operator: 'EQ', value: '+1' + $json.phoneNormalized }] }], properties: ['firstname','lastname','phone','mobilephone'], limit: 1 }) }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "contact-exists",
        "name": "Contact Exists?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          1920,
          0
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-total",
                "leftValue": "={{ $json.total }}",
                "rightValue": 0,
                "operator": {
                  "type": "number",
                  "operation": "gt"
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "update-hs-contact",
        "name": "Update HS Contact",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2160,
          -140
        ],
        "parameters": {
          "method": "PATCH",
          "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.results[0].id }}",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ properties: { phone: $('Normalize Phone').first().json.phoneFormatted } }) }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "set-contact-id",
        "name": "Set Contact ID",
        "type": "n8n-nodes-base.set",
        "typeVersion": 3.4,
        "position": [
          2400,
          -140
        ],
        "parameters": {
          "assignments": {
            "assignments": [
              {
                "id": "1",
                "name": "hsContactId",
                "value": "={{ $json.id }}",
                "type": "string"
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "has-company",
        "name": "Has Company?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          2640,
          0
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-company",
                "leftValue": "={{ $('Normalize Contact Data').first().json.company }}",
                "rightValue": "",
                "operator": {
                  "type": "string",
                  "operation": "notEmpty",
                  "singleValue": true
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "search-hs-company",
        "name": "Search HS Company",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2880,
          -100
        ],
        "parameters": {
          "method": "POST",
          "url": "https://api.hubapi.com/crm/v3/objects/companies/search",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'name', operator: 'EQ', value: $('Normalize Contact Data').first().json.company }] }], properties: ['name'], limit: 1 }) }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "company-exists",
        "name": "Company Exists?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          3120,
          -100
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-company-total",
                "leftValue": "={{ $json.total }}",
                "rightValue": 0,
                "operator": {
                  "type": "number",
                  "operation": "gt"
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "set-company-id",
        "name": "Set Company ID",
        "type": "n8n-nodes-base.set",
        "typeVersion": 3.4,
        "position": [
          3360,
          -220
        ],
        "parameters": {
          "assignments": {
            "assignments": [
              {
                "id": "1",
                "name": "hsCompanyId",
                "value": "={{ $json.results[0].id }}",
                "type": "string"
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "create-hs-company",
        "name": "Create HS Company",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          3360,
          20
        ],
        "parameters": {
          "method": "POST",
          "url": "https://api.hubapi.com/crm/v3/objects/companies",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ properties: { name: $('Normalize Contact Data').first().json.company } }) }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "set-new-company-id",
        "name": "Set New Company ID",
        "type": "n8n-nodes-base.set",
        "typeVersion": 3.4,
        "position": [
          3600,
          20
        ],
        "parameters": {
          "assignments": {
            "assignments": [
              {
                "id": "1",
                "name": "hsCompanyId",
                "value": "={{ $json.id }}",
                "type": "string"
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "get-all-ids",
        "name": "Get All IDs",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          3840,
          -100
        ],
        "parameters": {
          "jsCode": "// Purpose: Collect hsContactId + hsCompanyId for association\n// Inputs: Set Company ID or Set New Company ID, plus either Set Contact ID or Set New Contact ID\n// Outputs: hsContactId, hsCompanyId\n\nconst hsCompanyId = $input.first().json.hsCompanyId;\n\nlet hsContactId = null;\ntry { const v = $('Set Contact ID').first().json.hsContactId; if (v) hsContactId = v; } catch(e) {}\nif (!hsContactId) {\n  try { const v = $('Set New Contact ID').first().json.hsContactId; if (v) hsContactId = v; } catch(e) {}\n}\n\nif (!hsContactId || !hsCompanyId) {\n  throw new Error('Cannot associate - missing IDs. contactId=' + hsContactId + ', companyId=' + hsCompanyId);\n}\n\nreturn [{ json: { hsContactId, hsCompanyId } }];"
        }
      },
      {
        "id": "associate",
        "name": "Associate Contact + Company",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          4080,
          -100
        ],
        "parameters": {
          "method": "PUT",
          "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.hsContactId }}/associations/companies/{{ $json.hsCompanyId }}/contact_to_company",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "quo-contact-webhook",
        "name": "Quo Contact Updated Webhook",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          0,
          700
        ],
        "parameters": {
          "httpMethod": "POST",
          "path": "quo-contact-updated",
          "responseMode": "responseNode",
          "options": {}
        },
        "webhookId": "44864648-9ab5-4d61-999a-e7923cb2dbdd",
        "onError": "continueRegularOutput"
      },
      {
        "id": "respond-200-c2",
        "name": "Respond 200 (Contact)",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.1,
        "position": [
          240,
          700
        ],
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify({ received: true }) }}",
          "options": {
            "responseCode": 200
          }
        }
      },
      {
        "id": "parse-quo-contact",
        "name": "Parse Quo Contact Update",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          480,
          700
        ],
        "parameters": {
          "jsCode": "// Purpose: Extract contact fields from Quo contact.updated webhook\n// Inputs: Quo webhook body \u2014 firstName/lastName/company directly on data.object, phone at data.object.fields.Phone\n// Outputs: firstName, lastName, company, phone, email, quoContactId\n\nconst body = $input.first().json.body || $input.first().json;\nconst obj = (body.data && body.data.object) || {};\n\nconst firstName = obj.firstName || '';\nconst lastName = obj.lastName || '';\nconst company = obj.company || '';\nconst phone = (obj.fields && obj.fields.Phone) || '';\nconst email = (obj.fields && obj.fields.Email) || '';\nconst quoContactId = obj.id || '';\n\nreturn [{ json: { firstName, lastName, company, phone, email, quoContactId } }];"
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "has-phone-c2",
        "name": "Has Phone? (Contact)",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          720,
          700
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-phone-c2",
                "leftValue": "={{ $json.phone }}",
                "rightValue": "",
                "operator": {
                  "type": "string",
                  "operation": "notEmpty",
                  "singleValue": true
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "normalize-phone-c2",
        "name": "Normalize Phone (Contact)",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          960,
          700
        ],
        "parameters": {
          "jsCode": "// Purpose: Normalize raw phone from Quo contact for HubSpot search\n// Inputs: phone (raw E.164), all Quo contact fields in $json\n// Outputs: all Quo fields carried forward, phoneNormalized (10 digits), phoneFormatted\n\nconst raw = $json.phone || '';\nconst digits = raw.replace(/[^0-9]/g, '');\nconst phoneNormalized = (digits.length === 11 && digits.startsWith('1')) ? digits.slice(1) : digits;\nconst phoneFormatted = phoneNormalized.length === 10\n  ? '+1 (' + phoneNormalized.slice(0,3) + ') ' + phoneNormalized.slice(3,6) + '-' + phoneNormalized.slice(6)\n  : raw;\n\nreturn [{ json: { ...$json, phoneNormalized, phoneFormatted } }];"
        }
      },
      {
        "id": "search-hs-phone-c2",
        "name": "Search HS by Phone (Contact)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          1200,
          700
        ],
        "parameters": {
          "method": "POST",
          "url": "https://api.hubapi.com/crm/v3/objects/contacts/search",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'phone', operator: 'EQ', value: $json.phoneNormalized }] }, { filters: [{ propertyName: 'phone', operator: 'EQ', value: '+1' + $json.phoneNormalized }] }, { filters: [{ propertyName: 'phone', operator: 'EQ', value: $json.phoneFormatted }] }, { filters: [{ propertyName: 'mobilephone', operator: 'EQ', value: $json.phoneNormalized }] }, { filters: [{ propertyName: 'mobilephone', operator: 'EQ', value: '+1' + $json.phoneNormalized }] }], properties: ['firstname','lastname','company','phone','mobilephone','email'], limit: 1 }) }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "hs-found-c2",
        "name": "HS Contact Found?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          1440,
          700
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-hs-total-c2",
                "leftValue": "={{ $json.total }}",
                "rightValue": 0,
                "operator": {
                  "type": "number",
                  "operation": "gt"
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "build-hs-patch",
        "name": "Build HS Patch",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1680,
          560
        ],
        "parameters": {
          "jsCode": "// Purpose: Compare Quo contact fields with current HubSpot values; build minimal patch\n// Inputs: HS search result (results[0].properties), Normalize Phone (Contact) for Quo source data\n// Outputs: properties (changed fields only), hsContactId, changeCount\n\nconst hsResult = $input.first().json;\nconst hsContact = (hsResult.results && hsResult.results[0]) || {};\nconst hsProps = hsContact.properties || {};\nconst quoData = $('Normalize Phone (Contact)').first().json;\n\nconst candidates = [\n  ['firstname', quoData.firstName],\n  ['lastname', quoData.lastName],\n  ['company', quoData.company],\n  ['phone', quoData.phoneFormatted],\n  ['email', quoData.email]\n];\n\nconst props = {};\nfor (const [key, val] of candidates) {\n  if (val && (hsProps[key] || '') !== val) props[key] = val;\n}\n\nreturn [{ json: { properties: props, hsContactId: hsContact.id, changeCount: Object.keys(props).length } }];"
        }
      },
      {
        "id": "has-changes-c2",
        "name": "Has Changes? (Quo->HS)",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          1920,
          560
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-changes-c2",
                "leftValue": "={{ $json.changeCount }}",
                "rightValue": 0,
                "operator": {
                  "type": "number",
                  "operation": "gt"
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "patch-hs-c2",
        "name": "Patch HS Contact",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2160,
          560
        ],
        "parameters": {
          "method": "PATCH",
          "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.hsContactId }}",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ properties: $json.properties }) }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "hs-contact-webhook",
        "name": "HS Contact Change Webhook",
        "type": "n8n-nodes-base.webhook",
        "typeVersion": 2,
        "position": [
          0,
          1300
        ],
        "parameters": {
          "httpMethod": "POST",
          "path": "hs-contact-change",
          "responseMode": "responseNode",
          "options": {}
        },
        "webhookId": "81394b15-9cb9-449d-8f1e-d64ac26997bd",
        "onError": "continueRegularOutput"
      },
      {
        "id": "respond-200-c3",
        "name": "Respond 200 (HS)",
        "type": "n8n-nodes-base.respondToWebhook",
        "typeVersion": 1.1,
        "position": [
          240,
          1300
        ],
        "parameters": {
          "respondWith": "json",
          "responseBody": "={{ JSON.stringify({ received: true }) }}",
          "options": {
            "responseCode": 200
          }
        }
      },
      {
        "id": "parse-hs-event",
        "name": "Parse HS Event",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          480,
          1300
        ],
        "parameters": {
          "jsCode": "// Purpose: Extract contact ID and changed tracked fields from HubSpot propertyChange webhook\n// Inputs: HubSpot webhook payload (array of change events for one contact)\n// Outputs: objectId, changes (map of propertyName->value), changeCount\n\nconst body = $input.first().json.body || $input.first().json;\nconst events = Array.isArray(body) ? body : [body];\n\nconst trackedProps = new Set(['firstname', 'lastname', 'company', 'phone', 'email']);\nconst changes = {};\nlet objectId = null;\n\nfor (const ev of events) {\n  if (ev.objectId) objectId = String(ev.objectId);\n  if (trackedProps.has(ev.propertyName)) {\n    changes[ev.propertyName] = ev.propertyValue || '';\n  }\n}\n\nconst changeCount = Object.keys(changes).length;\nreturn [{ json: { objectId, changes, changeCount } }];"
        }
      },
      {
        "id": "tracked-prop",
        "name": "Tracked Property?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          720,
          1300
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-change-count",
                "leftValue": "={{ $json.changeCount }}",
                "rightValue": 0,
                "operator": {
                  "type": "number",
                  "operation": "gt"
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "get-hs-full",
        "name": "Get Full HS Contact",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          960,
          1300
        ],
        "parameters": {
          "method": "GET",
          "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.objectId }}?properties=firstname,lastname,company,phone,mobilephone,email",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "has-phone-c3",
        "name": "Has Phone? (HS)",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          1200,
          1300
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-hs-phone-c3",
                "leftValue": "={{ ($json.properties && ($json.properties.phone || $json.properties.mobilephone)) || '' }}",
                "rightValue": "",
                "operator": {
                  "type": "string",
                  "operation": "notEmpty",
                  "singleValue": true
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "normalize-phone-c3",
        "name": "Normalize Phone (HS)",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1440,
          1300
        ],
        "parameters": {
          "jsCode": "// Purpose: Normalize HubSpot contact phone for Quo lookup; carry all sync fields forward\n// Inputs: Get Full HS Contact response\n// Outputs: hsContactId, firstName, lastName, company, email, phone, phoneNormalized, phoneFormatted, changedFields\n\nconst hs = $input.first().json;\nconst props = hs.properties || {};\n\nconst raw = props.phone || props.mobilephone || '';\nconst digits = raw.replace(/[^0-9]/g, '');\nconst phoneNormalized = (digits.length === 11 && digits.startsWith('1')) ? digits.slice(1) : digits;\nconst phoneFormatted = phoneNormalized.length === 10\n  ? '+1 (' + phoneNormalized.slice(0,3) + ') ' + phoneNormalized.slice(3,6) + '-' + phoneNormalized.slice(6)\n  : raw;\n\nreturn [{ json: {\n  hsContactId: hs.id,\n  firstName: props.firstname || '',\n  lastName: props.lastname || '',\n  company: props.company || '',\n  email: props.email || '',\n  phone: raw,\n  phoneNormalized,\n  phoneFormatted,\n  changedFields: $('Parse HS Event').first().json.changes\n} }];"
        }
      },
      {
        "id": "fetch-quo-c3",
        "name": "Fetch Quo Contact (HS Change)",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          1680,
          1300
        ],
        "parameters": {
          "jsCode": "// Purpose: Find Quo contact by phone via pagination for HS->Quo sync\n// Inputs: Normalize Phone (HS) with phoneNormalized and all contact fields\n// Outputs: all HS fields carried forward, quoContact object, matchCount\n\nconst normData = $input.first().json;\nconst searchBase = normData.phoneNormalized || '';\n\nconst toBase = (p) => {\n  const d = (p || '').replace(/[^0-9]/g, '');\n  return (d.length === 11 && d.startsWith('1')) ? d.slice(1) : d;\n};\n\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\nconst MAX_PAGES = 60;\nlet pageToken = null;\nlet matched = null;\n\nfor (let i = 0; i < MAX_PAGES; i++) {\n  let url = 'https://api.openphone.com/v1/contacts?maxResults=50';\n  if (pageToken) url += '&pageToken=' + encodeURIComponent(pageToken);\n  let response;\n  try {\n    response = await this.helpers.httpRequest({\n      method: 'GET', url,\n      headers: { 'Authorization': QUO_API_KEY },\n      json: true\n    });\n  } catch (err) { break; }\n  const contacts = Array.isArray(response && response.data) ? response.data : [];\n  for (const c of contacts) {\n    const phones = Array.isArray(c && c.defaultFields && c.defaultFields.phoneNumbers) ? c.defaultFields.phoneNumbers : [];\n    if (phones.some(p => toBase(p.value) === searchBase)) { matched = c; break; }\n  }\n  if (matched) break;\n  pageToken = response && response.nextPageToken;\n  if (!pageToken) break;\n}\n\nreturn [{ json: { ...normData, quoContact: matched, matchCount: matched ? 1 : 0 } }];"
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "quo-found-c3",
        "name": "Quo Contact Found?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          1920,
          1300
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-quo-match",
                "leftValue": "={{ $json.matchCount }}",
                "rightValue": 0,
                "operator": {
                  "type": "number",
                  "operation": "gt"
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "build-quo-patch",
        "name": "Build Quo Patch",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2160,
          1160
        ],
        "parameters": {
          "jsCode": "// Purpose: Compare HS changed fields with current Quo contact; build minimal PUT body\n// Inputs: Fetch Quo Contact (HS Change) with quoContact and changedFields\n// Outputs: quoContactId, putBody (full merged defaultFields), changeCount\n\nconst data = $input.first().json;\nconst quoContact = data.quoContact || {};\nconst quoFields = quoContact.defaultFields || {};\nconst changedFields = data.changedFields || {};\n\nconst toBase = (p) => { const d = (p||'').replace(/[^0-9]/g,''); return (d.length===11&&d.startsWith('1'))?d.slice(1):d; };\nconst quoPhoneBase = toBase(((quoFields.phoneNumbers || [])[0] || {}).value || '');\nconst quoEmailVal = ((quoFields.emails || [])[0] || {}).value || '';\n\nconst mergedFields = { ...quoFields };\nlet changeCount = 0;\n\nif ('firstname' in changedFields && (quoFields.firstName || '') !== changedFields.firstname) {\n  mergedFields.firstName = changedFields.firstname; changeCount++;\n}\nif ('lastname' in changedFields && (quoFields.lastName || '') !== changedFields.lastname) {\n  mergedFields.lastName = changedFields.lastname; changeCount++;\n}\nif ('company' in changedFields && (quoFields.company || '') !== changedFields.company) {\n  mergedFields.company = changedFields.company; changeCount++;\n}\nif ('phone' in changedFields && toBase(changedFields.phone) !== quoPhoneBase) {\n  mergedFields.phoneNumbers = [{ value: changedFields.phone }]; changeCount++;\n}\nif ('email' in changedFields && quoEmailVal !== changedFields.email) {\n  mergedFields.emails = [{ value: changedFields.email }]; changeCount++;\n}\n\nreturn [{ json: { quoContactId: quoContact.id, putBody: { defaultFields: mergedFields }, changeCount } }];"
        }
      },
      {
        "id": "has-changes-c3",
        "name": "Has Changes? (HS->Quo)",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          2400,
          1160
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-changes-c3",
                "leftValue": "={{ $json.changeCount }}",
                "rightValue": 0,
                "operator": {
                  "type": "number",
                  "operation": "gt"
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "patch-quo-c3",
        "name": "Patch Quo Contact",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2640,
          1160
        ],
        "parameters": {
          "jsCode": "// Purpose: Update existing Quo contact via OpenPhone API (PUT with merged fields)\n// Inputs: Build Quo Patch with quoContactId and putBody\n// Outputs: success confirmation\n\nconst data = $input.first().json;\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\n\nawait this.helpers.httpRequest({\n  method: 'PUT',\n  url: `https://api.openphone.com/v1/contacts/${data.quoContactId}`,\n  headers: { 'Authorization': QUO_API_KEY, 'Content-Type': 'application/json' },\n  body: JSON.stringify(data.putBody),\n  json: false\n});\n\nreturn [{ json: { success: true, quoContactId: data.quoContactId } }];"
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "create-quo-c3",
        "name": "Create Quo Contact",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2160,
          1440
        ],
        "parameters": {
          "jsCode": "// Purpose: Create new Quo contact from HubSpot data when no Quo match exists\n// Inputs: Fetch Quo Contact (HS Change) with HS field values\n// Outputs: success confirmation\n\nconst data = $input.first().json;\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\n\nconst defaultFields = {};\nif (data.firstName) defaultFields.firstName = data.firstName;\nif (data.lastName) defaultFields.lastName = data.lastName;\nif (data.company) defaultFields.company = data.company;\nif (data.phoneFormatted) defaultFields.phoneNumbers = [{ value: data.phoneFormatted }];\nif (data.email) defaultFields.emails = [{ value: data.email }];\n\nawait this.helpers.httpRequest({\n  method: 'POST',\n  url: 'https://api.openphone.com/v1/contacts',\n  headers: { 'Authorization': QUO_API_KEY, 'Content-Type': 'application/json' },\n  body: JSON.stringify({ defaultFields }),\n  json: false\n});\n\nreturn [{ json: { success: true, created: true } }];"
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "has-company-c2",
        "name": "Has Company? (C2)",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          2400,
          560
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-company-c2",
                "leftValue": "={{ $('Normalize Phone (Contact)').first().json.company }}",
                "rightValue": "",
                "operator": {
                  "type": "string",
                  "operation": "notEmpty",
                  "singleValue": true
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "search-hs-company-c2",
        "name": "Search HS Company (C2)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2640,
          420
        ],
        "parameters": {
          "method": "POST",
          "url": "https://api.hubapi.com/crm/v3/objects/companies/search",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ filterGroups: [{ filters: [{ propertyName: 'name', operator: 'EQ', value: $('Normalize Phone (Contact)').first().json.company }] }], properties: ['name'], limit: 1 }) }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "company-exists-c2",
        "name": "Company Exists? (C2)",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          2880,
          420
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-company-total-c2",
                "leftValue": "={{ $json.total }}",
                "rightValue": 0,
                "operator": {
                  "type": "number",
                  "operation": "gt"
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "set-company-id-c2",
        "name": "Set Company ID (C2)",
        "type": "n8n-nodes-base.set",
        "typeVersion": 3.4,
        "position": [
          3120,
          300
        ],
        "parameters": {
          "assignments": {
            "assignments": [
              {
                "id": "1",
                "name": "hsCompanyId",
                "value": "={{ $json.results[0].id }}",
                "type": "string"
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "create-hs-company-c2",
        "name": "Create HS Company (C2)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          3120,
          540
        ],
        "parameters": {
          "method": "POST",
          "url": "https://api.hubapi.com/crm/v3/objects/companies",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ properties: { name: $('Normalize Phone (Contact)').first().json.company } }) }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "set-new-company-id-c2",
        "name": "Set New Company ID (C2)",
        "type": "n8n-nodes-base.set",
        "typeVersion": 3.4,
        "position": [
          3360,
          540
        ],
        "parameters": {
          "assignments": {
            "assignments": [
              {
                "id": "1",
                "name": "hsCompanyId",
                "value": "={{ $json.id }}",
                "type": "string"
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "get-all-ids-c2",
        "name": "Get All IDs (C2)",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          3600,
          420
        ],
        "parameters": {
          "jsCode": "// Purpose: Collect hsContactId + hsCompanyId for association in Chain 2\n// Inputs: Set Company ID (C2) or Set New Company ID (C2); Build HS Patch for contact ID\n// Outputs: hsContactId, hsCompanyId\n\nconst hsCompanyId = $input.first().json.hsCompanyId;\nconst hsContactId = $('Build HS Patch').first().json.hsContactId;\n\nif (!hsContactId || !hsCompanyId) {\n  throw new Error('Cannot associate - missing IDs. contactId=' + hsContactId + ', companyId=' + hsCompanyId);\n}\n\nreturn [{ json: { hsContactId, hsCompanyId } }];"
        }
      },
      {
        "id": "associate-c2",
        "name": "Associate Contact + Company (C2)",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          3840,
          420
        ],
        "parameters": {
          "method": "PUT",
          "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $json.hsContactId }}/associations/companies/{{ $json.hsCompanyId }}/contact_to_company",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "get-old-assoc-c2",
        "name": "Get Old Company Associations",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2500,
          700
        ],
        "parameters": {
          "method": "GET",
          "url": "=https://api.hubapi.com/crm/v3/objects/contacts/{{ $('Build HS Patch').first().json.hsContactId }}/associations/companies",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "remove-old-assoc-c2",
        "name": "Remove Old Company Associations",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2780,
          700
        ],
        "parameters": {
          "method": "POST",
          "url": "https://api.hubapi.com/crm/v4/associations/contacts/companies/batch/archive",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ inputs: ($json.results || []).map(r => ({ from: { id: String($('Build HS Patch').first().json.hsContactId) }, to: [{ id: String(r.id) }] })) }) }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "fetch-call-details",
        "name": "Fetch Call Details",
        "type": "n8n-nodes-base.code",
        "typeVersion": 2,
        "position": [
          2160,
          280
        ],
        "parameters": {
          "jsCode": "// Purpose: Fetch call summary/transcript from OpenPhone for AI prospect classification\n// Inputs: Parse Quo Payload (callId)\n// Outputs: callId, summary, hasSummary\n\nconst QUO_API_KEY = 'PMdBWaig1oFVhOBinL42xxAdKlqxfuSX';\nconst callId = $('Parse Quo Payload').first().json.callId;\n\nif (!callId) return [{ json: { callId: null, summary: '', hasSummary: false } }];\n\nlet text = '';\ntry {\n  const response = await this.helpers.httpRequest({\n    method: 'GET',\n    url: `https://api.openphone.com/v1/calls/${callId}`,\n    headers: { 'Authorization': QUO_API_KEY },\n    json: true\n  });\n  const data = response.data || {};\n  const summary = data.summary || '';\n  const dialogue = (data.transcription && Array.isArray(data.transcription.dialogue))\n    ? data.transcription.dialogue.map(d => `${d.speaker}: ${d.text}`).join(' ')\n    : '';\n  text = summary || dialogue;\n} catch (err) {}\n\nreturn [{ json: { callId, summary: text, hasSummary: !!text } }];"
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "has-summary",
        "name": "Has Call Summary?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          2400,
          280
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-summary",
                "leftValue": "={{ $json.hasSummary }}",
                "rightValue": true,
                "operator": {
                  "type": "boolean",
                  "operation": "true",
                  "singleValue": true
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "ai-classify-caller",
        "name": "AI Classify Caller",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          2640,
          280
        ],
        "parameters": {
          "method": "POST",
          "url": "https://api.openai.com/v1/chat/completions",
          "authentication": "predefinedCredentialType",
          "nodeCredentialType": "openAiApi",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'system', content: 'You are a sales qualifier for Renellence, a Canadian medical device company selling to healthcare facilities, clinics, and medical professionals. Analyze the call summary and determine if this caller is a potential business prospect \u2014 a client, healthcare professional, partner, or referral source. Answer YES if there is any business relevance to a medical device company. Answer NO if the caller is clearly unrelated \u2014 e.g. personal call, wrong number, spam, construction worker, delivery driver, or other non-medical tradesperson. Respond with only YES or NO, nothing else.' }, { role: 'user', content: 'Call summary: ' + $json.summary }], max_tokens: 5 }) }}",
          "options": {}
        },
        "credentials": {
          "openAiApi": {
            "id": "hgtnBYAfFHRxn20h",
            "name": "OpenAi account"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "is-prospect",
        "name": "Is Prospect?",
        "type": "n8n-nodes-base.if",
        "typeVersion": 2.2,
        "position": [
          2880,
          280
        ],
        "parameters": {
          "conditions": {
            "options": {
              "version": 2,
              "leftValue": "",
              "caseSensitive": false,
              "typeValidation": "loose"
            },
            "combinator": "and",
            "conditions": [
              {
                "id": "check-prospect",
                "leftValue": "={{ ($json.choices && $json.choices[0] && $json.choices[0].message && $json.choices[0].message.content) || '' }}",
                "rightValue": "YES",
                "operator": {
                  "type": "string",
                  "operation": "contains"
                }
              }
            ]
          },
          "options": {}
        }
      },
      {
        "id": "create-hs-contact",
        "name": "Create HS Contact",
        "type": "n8n-nodes-base.httpRequest",
        "typeVersion": 4.2,
        "position": [
          3120,
          280
        ],
        "parameters": {
          "method": "POST",
          "url": "https://api.hubapi.com/crm/v3/objects/contacts",
          "authentication": "genericCredentialType",
          "genericAuthType": "httpHeaderAuth",
          "sendHeaders": true,
          "headerParameters": {
            "parameters": [
              {
                "name": "Content-Type",
                "value": "application/json"
              }
            ]
          },
          "sendBody": true,
          "specifyBody": "json",
          "jsonBody": "={{ (() => { const d = $('Normalize Contact Data').first().json; const props = { phone: d.phoneFormatted }; if (d.firstName) props.firstname = d.firstName; if (d.lastName) props.lastname = d.lastName; return JSON.stringify({ properties: props }); })() }}",
          "options": {}
        },
        "credentials": {
          "httpHeaderAuth": {
            "id": "EGBZ7rLRax2MyoAR",
            "name": "HubSpot - Renellence"
          }
        },
        "onError": "continueRegularOutput"
      },
      {
        "id": "set-new-contact-id",
        "name": "Set New Contact ID",
        "type": "n8n-nodes-base.set",
        "typeVersion": 3.4,
        "position": [
          3360,
          280
        ],
        "parameters": {
          "assignments": {
            "assignments": [
              {
                "id": "1",
                "name": "hsContactId",
                "value": "={{ $json.id }}",
                "type": "string"
              }
            ]
          },
          "options": {}
        }
      }
    ],
    "connections": {
      "Quo Call Webhook": {
        "main": [
          [
            {
              "node": "Respond 200",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Respond 200": {
        "main": [
          [
            {
              "node": "Parse Quo Payload",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Parse Quo Payload": {
        "main": [
          [
            {
              "node": "Has Phone?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Has Phone?": {
        "main": [
          [
            {
              "node": "Normalize Phone",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Normalize Phone": {
        "main": [
          [
            {
              "node": "Fetch Quo Contact",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Fetch Quo Contact": {
        "main": [
          [
            {
              "node": "Normalize Contact Data",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Normalize Contact Data": {
        "main": [
          [
            {
              "node": "Search HS by Phone",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Search HS by Phone": {
        "main": [
          [
            {
              "node": "Contact Exists?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Contact Exists?": {
        "main": [
          [
            {
              "node": "Update HS Contact",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Fetch Call Details",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Update HS Contact": {
        "main": [
          [
            {
              "node": "Set Contact ID",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Set Contact ID": {
        "main": [
          [
            {
              "node": "Has Company?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Has Company?": {
        "main": [
          [
            {
              "node": "Search HS Company",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Search HS Company": {
        "main": [
          [
            {
              "node": "Company Exists?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Company Exists?": {
        "main": [
          [
            {
              "node": "Set Company ID",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Create HS Company",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Set Company ID": {
        "main": [
          [
            {
              "node": "Get All IDs",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Create HS Company": {
        "main": [
          [
            {
              "node": "Set New Company ID",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Set New Company ID": {
        "main": [
          [
            {
              "node": "Get All IDs",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Get All IDs": {
        "main": [
          [
            {
              "node": "Associate Contact + Company",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Quo Contact Updated Webhook": {
        "main": [
          [
            {
              "node": "Respond 200 (Contact)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Respond 200 (Contact)": {
        "main": [
          [
            {
              "node": "Parse Quo Contact Update",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Parse Quo Contact Update": {
        "main": [
          [
            {
              "node": "Has Phone? (Contact)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Has Phone? (Contact)": {
        "main": [
          [
            {
              "node": "Normalize Phone (Contact)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Normalize Phone (Contact)": {
        "main": [
          [
            {
              "node": "Search HS by Phone (Contact)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Search HS by Phone (Contact)": {
        "main": [
          [
            {
              "node": "HS Contact Found?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "HS Contact Found?": {
        "main": [
          [
            {
              "node": "Build HS Patch",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Build HS Patch": {
        "main": [
          [
            {
              "node": "Has Changes? (Quo->HS)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Has Changes? (Quo->HS)": {
        "main": [
          [
            {
              "node": "Patch HS Contact",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "HS Contact Change Webhook": {
        "main": [
          [
            {
              "node": "Respond 200 (HS)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Respond 200 (HS)": {
        "main": [
          [
            {
              "node": "Parse HS Event",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Parse HS Event": {
        "main": [
          [
            {
              "node": "Tracked Property?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Tracked Property?": {
        "main": [
          [
            {
              "node": "Get Full HS Contact",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Get Full HS Contact": {
        "main": [
          [
            {
              "node": "Has Phone? (HS)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Has Phone? (HS)": {
        "main": [
          [
            {
              "node": "Normalize Phone (HS)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Normalize Phone (HS)": {
        "main": [
          [
            {
              "node": "Fetch Quo Contact (HS Change)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Fetch Quo Contact (HS Change)": {
        "main": [
          [
            {
              "node": "Quo Contact Found?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Quo Contact Found?": {
        "main": [
          [
            {
              "node": "Build Quo Patch",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Create Quo Contact",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Build Quo Patch": {
        "main": [
          [
            {
              "node": "Has Changes? (HS->Quo)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Has Changes? (HS->Quo)": {
        "main": [
          [
            {
              "node": "Patch Quo Contact",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Patch HS Contact": {
        "main": [
          [
            {
              "node": "Has Company? (C2)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Search HS Company (C2)": {
        "main": [
          [
            {
              "node": "Company Exists? (C2)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Company Exists? (C2)": {
        "main": [
          [
            {
              "node": "Set Company ID (C2)",
              "type": "main",
              "index": 0
            }
          ],
          [
            {
              "node": "Create HS Company (C2)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Set Company ID (C2)": {
        "main": [
          [
            {
              "node": "Get All IDs (C2)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Create HS Company (C2)": {
        "main": [
          [
            {
              "node": "Set New Company ID (C2)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Set New Company ID (C2)": {
        "main": [
          [
            {
              "node": "Get All IDs (C2)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Get All IDs (C2)": {
        "main": [
          [
            {
              "node": "Associate Contact + Company (C2)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Has Company? (C2)": {
        "main": [
          [
            {
              "node": "Get Old Company Associations",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Get Old Company Associations": {
        "main": [
          [
            {
              "node": "Remove Old Company Associations",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Remove Old Company Associations": {
        "main": [
          [
            {
              "node": "Search HS Company (C2)",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Fetch Call Details": {
        "main": [
          [
            {
              "node": "Has Call Summary?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Has Call Summary?": {
        "main": [
          [
            {
              "node": "AI Classify Caller",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "AI Classify Caller": {
        "main": [
          [
            {
              "node": "Is Prospect?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Is Prospect?": {
        "main": [
          [
            {
              "node": "Create HS Contact",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Create HS Contact": {
        "main": [
          [
            {
              "node": "Set New Contact ID",
              "type": "main",
              "index": 0
            }
          ]
        ]
      },
      "Set New Contact ID": {
        "main": [
          [
            {
              "node": "Has Company?",
              "type": "main",
              "index": 0
            }
          ]
        ]
      }
    },
    "authors": "aria moghimi",
    "name": null,
    "description": null
  }
}