{
  "name": "Lumeor | HubSpot company to QuickBooks Online customer sync",
  "nodes": [
    {
      "parameters": {
        "content": "## One-way HubSpot to QuickBooks Online customer sync\n\nWhen a HubSpot company changes, the matching QuickBooks Online customer is updated, or created if none exists. The QuickBooks ID is stored on the HubSpot company, so there are no duplicates. A hash of the synced fields is stored too, so changes to fields QuickBooks does not care about are skipped. Changes to the sync's own properties are ignored, which prevents loops. When there is no stored ID, Claude scores fuzzy name matches against QuickBooks candidates; uncertain matches go to Slack for a person to decide.\n\n### Setup\n1. In HubSpot, create three company properties: `qbo_customer_id`, `qbo_sync_hash`, `qbo_last_synced_at` (single-line text).\n2. Create a HubSpot private app with `crm.objects.companies.read` and `.write`, and add its token as an n8n HubSpot App Token credential. Add a webhook subscription in the private app's Webhooks tab (or a developer app) for `company.propertyChange` (name, domain, phone, address, city, state, zip) pointed at the **HubSpot company changed** webhook URL.\n3. Replace `REPLACE_WITH_QBO_REALM_ID` on **QuickBooks: find candidates**. Use the sandbox host `sandbox-quickbooks.api.intuit.com` while testing.\n4. Set `REPLACE_WITH_SLACK_CHANNEL_ID` on the Slack node.\n5. Credentials: HubSpot App Token, QuickBooks Online OAuth2, Anthropic API, Slack.\n\nField ownership: HubSpot owns name, phone and billing address. Edit `FIELD_MAP` in **Map fields and detect changes** to change that.\n\nThe match step uses `claude-opus-5-5` at low effort. `claude-haiku-5-5` is a cheaper option for high-volume, simple scoring like this.\n\nNot included: nightly reconciliation, deletes and merges. Add them before relying on the sync for finance reporting.\n\nTemplate by Lumeor Studio, https://www.lumeor.studio/guides/automate-data-sync-between-apps/",
        "height": 900,
        "width": 540,
        "color": 7
      },
      "id": "fbe0b739-3db8-4007-97a1-fff851c99f66",
      "name": "About this template",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -560,
        -260
      ]
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "hubspot-company-sync",
        "options": {}
      },
      "id": "61e30ebc-67d3-41ed-b822-bb171d8750eb",
      "name": "HubSpot company changed",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        0,
        300
      ],
      "webhookId": "d1babb30-89d6-4271-9b89-36a12820b71c"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// HubSpot sends an array of events. Keep one item per company and drop changes the sync made itself.\nconst SYNC_OWNED = ['qbo_customer_id', 'qbo_sync_hash', 'qbo_last_synced_at'];\nconst WATCHED = ['name', 'domain', 'phone', 'address', 'city', 'state', 'zip', 'country'];\nconst companies = new Map();\nfor (const item of $input.all()) {\n  const events = Array.isArray(item.json.body) ? item.json.body : [item.json.body];\n  for (const e of events) {\n    if (!e || !e.objectId) continue;\n    if (e.propertyName && SYNC_OWNED.includes(e.propertyName)) continue;\n    if (e.propertyName && !WATCHED.includes(e.propertyName)) continue;\n    companies.set(String(e.objectId), { companyId: String(e.objectId), lastEventAt: e.occurredAt || null });\n  }\n}\nreturn [...companies.values()].map((json) => ({ json }));\n"
      },
      "id": "db225204-ca04-4a74-bff0-901e200e7847",
      "name": "Filter events (loop prevention)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        220,
        300
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.hubapi.com/crm/v3/objects/companies/{{ $json.companyId }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotAppToken",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "properties",
              "value": "name,domain,phone,address,city,state,zip,country,qbo_customer_id,qbo_sync_hash"
            }
          ]
        },
        "options": {
          "timeout": 20000
        }
      },
      "id": "78646331-d323-471b-b1a4-de928e68c34c",
      "name": "HubSpot: get company",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        300
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Fields HubSpot owns, mapped to QuickBooks customer fields.\nconst FIELD_MAP = { name: 'CompanyName', phone: 'PrimaryPhone', address: 'Line1', city: 'City', state: 'CountrySubDivisionCode', zip: 'PostalCode' };\n// Small, stable string hash (djb2). Good enough to detect \"nothing we sync has changed\".\nconst hash = (s) => { let h = 5381; for (let i = 0; i < s.length; i++) h = ((h << 5) + h + s.charCodeAt(i)) >>> 0; return h.toString(16); };\nconst clean = (v) => (v == null ? '' : String(v).trim());\n// Picklist mapping: HubSpot often stores full state names, QuickBooks expects 2-letter codes.\nconst STATES = { alabama: 'AL', alaska: 'AK', arizona: 'AZ', arkansas: 'AR', california: 'CA', colorado: 'CO', connecticut: 'CT', delaware: 'DE', 'district of columbia': 'DC', florida: 'FL', georgia: 'GA', hawaii: 'HI', idaho: 'ID', illinois: 'IL', indiana: 'IN', iowa: 'IA', kansas: 'KS', kentucky: 'KY', louisiana: 'LA', maine: 'ME', maryland: 'MD', massachusetts: 'MA', michigan: 'MI', minnesota: 'MN', mississippi: 'MS', missouri: 'MO', montana: 'MT', nebraska: 'NE', nevada: 'NV', 'new hampshire': 'NH', 'new jersey': 'NJ', 'new mexico': 'NM', 'new york': 'NY', 'north carolina': 'NC', 'north dakota': 'ND', ohio: 'OH', oklahoma: 'OK', oregon: 'OR', pennsylvania: 'PA', 'rhode island': 'RI', 'south carolina': 'SC', 'south dakota': 'SD', tennessee: 'TN', texas: 'TX', utah: 'UT', vermont: 'VT', virginia: 'VA', washington: 'WA', 'west virginia': 'WV', wisconsin: 'WI', wyoming: 'WY' };\nconst stateCode = (v) => { const s = clean(v); return STATES[s.toLowerCase()] || s.toUpperCase(); };\n\nreturn $input.all().map((item, i) => {\n  const p = item.json.properties || {};\n  const mapped = {};\n  for (const [hs, qbo] of Object.entries(FIELD_MAP)) mapped[qbo] = clean(p[hs]);\n  mapped.CountrySubDivisionCode = stateCode(p.state);\n  mapped.DisplayName = mapped.CompanyName;\n  const syncHash = hash(JSON.stringify(mapped));\n\n  let action;\n  let reason = null;\n  if (!mapped.CompanyName) { action = 'exception'; reason = 'Company has no name'; }\n  else if (mapped.CountrySubDivisionCode && mapped.CountrySubDivisionCode.length > 3) { action = 'exception'; reason = 'State \"' + mapped.CountrySubDivisionCode + '\" could not be mapped to a 2-letter code'; }\n  else if (p.qbo_sync_hash && p.qbo_sync_hash === syncHash) action = 'skip';\n  else if (p.qbo_customer_id) action = 'update';\n  else action = 'match';\n\n  return {\n    json: { hubspotId: String(item.json.id), qboCustomerId: clean(p.qbo_customer_id) || null, domain: clean(p.domain), mapped, syncHash, action, reason },\n    pairedItem: { item: i },\n  };\n});\n"
      },
      "id": "8344b20e-858c-4459-af0e-e33b58d107d5",
      "name": "Map fields and detect changes",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        660,
        300
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "49dda408-bf43-4344-8b8c-3df39144273d",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "update",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "update"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "5cea80bf-769c-4ae4-bf94-17bb06cd08ba",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "match",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "match"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "16709c5f-437b-4e79-b6d6-6e5906525d1b",
                    "leftValue": "={{ $json.action }}",
                    "rightValue": "exception",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "exception"
            }
          ]
        },
        "options": {}
      },
      "id": "9f6c621a-f84c-4663-8472-e0bb271e7953",
      "name": "Sync action",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        880,
        300
      ]
    },
    {
      "parameters": {
        "resource": "customer",
        "operation": "update",
        "customerId": "={{ $json.qboCustomerId }}",
        "updateFields": {
          "CompanyName": "={{ $json.mapped.CompanyName }}",
          "PrimaryPhone": "={{ $json.mapped.PrimaryPhone }}",
          "BillAddr": {
            "details": {
              "Line1": "={{ $json.mapped.Line1 }}",
              "City": "={{ $json.mapped.City }}",
              "CountrySubDivisionCode": "={{ $json.mapped.CountrySubDivisionCode }}",
              "PostalCode": "={{ $json.mapped.PostalCode }}"
            }
          }
        }
      },
      "id": "dc3c9fa7-092f-4b27-b791-517cbfeaf2a9",
      "name": "QuickBooks: update customer",
      "type": "n8n-nodes-base.quickbooks",
      "typeVersion": 1,
      "position": [
        1100,
        100
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "GET",
        "url": "https://quickbooks.api.intuit.com/v3/company/REPLACE_WITH_QBO_REALM_ID/query",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "quickBooksOAuth2Api",
        "sendQuery": true,
        "queryParameters": {
          "parameters": [
            {
              "name": "query",
              "value": "={{ \"select * from Customer where DisplayName like '%\" + $json.mapped.CompanyName.split(/\\s+/)[0].replace(/'/g, \"\\\\'\") + \"%' maxresults 10\" }}"
            },
            {
              "name": "minorversion",
              "value": "75"
            }
          ]
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "options": {
          "timeout": 20000
        }
      },
      "id": "3d7f9886-d641-47a1-bc3e-4163d596d852",
      "name": "QuickBooks: find candidates",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1100,
        300
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "const schema = {\n  type: 'object',\n  additionalProperties: false,\n  required: ['best_match_id', 'confidence', 'reason'],\n  properties: {\n    best_match_id: { anyOf: [{ type: 'string' }, { type: 'null' }], description: 'QuickBooks Id of the same legal business, or null if none is the same business.' },\n    confidence: { type: 'string', enum: ['high', 'medium', 'low'] },\n    reason: { type: 'string' },\n  },\n};\nreturn $input.all().map((item, i) => {\n  const src = $('Map fields and detect changes').itemMatching(i).json;\n  const candidates = (item.json.QueryResponse?.Customer || []).map((c) => ({\n    Id: c.Id, DisplayName: c.DisplayName, CompanyName: c.CompanyName || null,\n    Phone: c.PrimaryPhone?.FreeFormNumber || null, Email: c.PrimaryEmailAddr?.Address || null,\n    City: c.BillAddr?.City || null, State: c.BillAddr?.CountrySubDivisionCode || null, Active: c.Active,\n  }));\n  return {\n    json: {\n      ...src,\n      candidates,\n      claudeRequest: {\n        model: 'claude-opus-5-5',\n        max_tokens: 1000,\n        system: 'You decide whether a CRM company is the same business as one of the accounting customers listed. Treat suffixes like Inc and LLC, abbreviations and punctuation as equivalent. Different locations of a chain, or a parent and a subsidiary, are not the same customer. If unsure, return null with low confidence.',\n        messages: [{ role: 'user', content: 'CRM company:\\n' + JSON.stringify({ ...src.mapped, domain: src.domain }) + '\\n\\nAccounting customers:\\n' + JSON.stringify(candidates) }],\n        output_config: { effort: 'low', format: { type: 'json_schema', schema } },\n      },\n    },\n    pairedItem: { item: i },\n  };\n});\n"
      },
      "id": "eb7243a5-1f83-4817-925b-f1d434712699",
      "name": "Build match prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1320,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.anthropic.com/v1/messages",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "anthropicApi",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "anthropic-version",
              "value": "2023-06-01"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify($json.claudeRequest) }}",
        "options": {
          "timeout": 120000
        }
      },
      "id": "1956f026-fe1a-48c7-8812-12fcd423d8cd",
      "name": "Claude: score fuzzy match",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1540,
        300
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 5000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "language": "javaScript",
        "jsCode": "// Pull the structured JSON out of a Messages API response.\n// Opus 5.5 always thinks, so a (possibly empty) thinking block can come before the text block.\nfunction parseClaude(res) {\n  if (!res || typeof res !== 'object') return { error: 'Empty response from the Messages API' };\n  if (res.stop_reason === 'refusal') {\n    return { error: 'Model declined the request (refusal' + (res.stop_details?.category ? ': ' + res.stop_details.category : '') + ')' };\n  }\n  if (res.stop_reason === 'max_tokens') return { error: 'Response hit max_tokens before the JSON was complete' };\n  const block = (res.content || []).find((b) => b.type === 'text');\n  if (!block) return { error: 'No text block in the response' };\n  try {\n    return { data: JSON.parse(block.text) };\n  } catch (e) {\n    return { error: 'Could not parse model JSON: ' + e.message };\n  }\n}\n\nreturn $input.all().map((item, i) => {\n  const src = $('Build match prompt').itemMatching(i).json;\n  const { data, error } = parseClaude(item.json);\n  let decision; let reason; let matchedId = null; let rest_qbo = null;\n  if (!src.candidates.length) { decision = 'create'; reason = 'No similar customer in QuickBooks'; }\n  else if (error) { decision = 'review'; reason = error; }\n  else if (data.best_match_id && data.confidence === 'high' && src.candidates.some((c) => c.Id === data.best_match_id)) {\n    decision = 'link'; matchedId = data.best_match_id; reason = data.reason;\n    rest_qbo = matchedId;\n  } else if (!data.best_match_id && data.confidence === 'high') { decision = 'create'; reason = data.reason; }\n  else { decision = 'review'; reason = 'Uncertain match (' + data.confidence + '): ' + data.reason; }\n  const { claudeRequest, ...rest } = src;\n  // A linked customer goes through the update path so its fields match HubSpot before the hash is stored.\n  return { json: { ...rest, qboCustomerId: rest_qbo, decision, matchedId, reason }, pairedItem: { item: i } };\n});\n"
      },
      "id": "79e8d86d-ccd0-46c1-a47c-3f5d25f48da9",
      "name": "Decide match",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1760,
        300
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "f81c6295-f69d-455b-bd8f-72ab599cb271",
                    "leftValue": "={{ $json.decision }}",
                    "rightValue": "create",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "create"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "b4a7eaf7-d4b9-4481-9a64-0887f31def19",
                    "leftValue": "={{ $json.decision }}",
                    "rightValue": "link",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "link existing"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict",
                  "version": 2
                },
                "conditions": [
                  {
                    "id": "47992303-2361-4ca8-8dd4-cd1a20b69eee",
                    "leftValue": "={{ $json.decision }}",
                    "rightValue": "review",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "renameOutput": true,
              "outputKey": "needs review"
            }
          ]
        },
        "options": {}
      },
      "id": "e6e86aed-c8f2-4b89-8571-f2c33cfedc87",
      "name": "Match result",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        1980,
        300
      ]
    },
    {
      "parameters": {
        "resource": "customer",
        "operation": "create",
        "displayName": "={{ $json.mapped.DisplayName }}",
        "additionalFields": {
          "CompanyName": "={{ $json.mapped.CompanyName }}",
          "PrimaryPhone": "={{ $json.mapped.PrimaryPhone }}",
          "BillAddr": {
            "details": {
              "Line1": "={{ $json.mapped.Line1 }}",
              "City": "={{ $json.mapped.City }}",
              "CountrySubDivisionCode": "={{ $json.mapped.CountrySubDivisionCode }}",
              "PostalCode": "={{ $json.mapped.PostalCode }}"
            }
          }
        }
      },
      "id": "64466e3c-e9fe-41ff-93fc-10e31c62349c",
      "name": "QuickBooks: create customer",
      "type": "n8n-nodes-base.quickbooks",
      "typeVersion": 1,
      "position": [
        2200,
        200
      ],
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "method": "PATCH",
        "url": "=https://api.hubapi.com/crm/v3/objects/companies/{{ $('Map fields and detect changes').item.json.hubspotId }}",
        "authentication": "predefinedCredentialType",
        "nodeCredentialType": "hubspotAppToken",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ properties: { qbo_customer_id: String($json.Id), qbo_sync_hash: $('Map fields and detect changes').item.json.syncHash, qbo_last_synced_at: $now.toISO() } }) }}",
        "options": {
          "timeout": 20000
        }
      },
      "id": "bdb83799-1126-4c9c-b266-7c7af2d848f4",
      "name": "HubSpot: store QuickBooks ID and hash",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2420,
        100
      ],
      "retryOnFail": true,
      "maxTries": 3,
      "waitBetweenTries": 3000,
      "onError": "continueErrorOutput"
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "post",
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "REPLACE_WITH_SLACK_CHANNEL_ID",
          "mode": "id"
        },
        "text": "=:warning: HubSpot to QuickBooks sync needs attention\nHubSpot company: {{ $('Filter events (loop prevention)').item.json.companyId }} {{ $json.mapped?.CompanyName ?? '' }}\nReason: {{ $json.reason ?? $json.error?.message ?? JSON.stringify($json.error ?? $json) }}\n{{ $json.candidates?.length ? 'Candidates: ' + $json.candidates.map(c => c.Id + ' ' + c.DisplayName).join(', ') : '' }}\nhttps://app.hubspot.com/contacts/REPLACE_WITH_HUBSPOT_PORTAL_ID/company/{{ $('Filter events (loop prevention)').item.json.companyId }}",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "id": "439cda84-793a-4edb-a12a-bf072310fa21",
      "name": "Slack: sync exceptions",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        2200,
        520
      ]
    }
  ],
  "connections": {
    "HubSpot company changed": {
      "main": [
        [
          {
            "node": "Filter events (loop prevention)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter events (loop prevention)": {
      "main": [
        [
          {
            "node": "HubSpot: get company",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: get company": {
      "main": [
        [
          {
            "node": "Map fields and detect changes",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: sync exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Map fields and detect changes": {
      "main": [
        [
          {
            "node": "Sync action",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sync action": {
      "main": [
        [
          {
            "node": "QuickBooks: update customer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "QuickBooks: find candidates",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: sync exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QuickBooks: update customer": {
      "main": [
        [
          {
            "node": "HubSpot: store QuickBooks ID and hash",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: sync exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QuickBooks: find candidates": {
      "main": [
        [
          {
            "node": "Build match prompt",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: sync exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build match prompt": {
      "main": [
        [
          {
            "node": "Claude: score fuzzy match",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Claude: score fuzzy match": {
      "main": [
        [
          {
            "node": "Decide match",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: sync exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Decide match": {
      "main": [
        [
          {
            "node": "Match result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Match result": {
      "main": [
        [
          {
            "node": "QuickBooks: create customer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "QuickBooks: update customer",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: sync exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "QuickBooks: create customer": {
      "main": [
        [
          {
            "node": "HubSpot: store QuickBooks ID and hash",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack: sync exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HubSpot: store QuickBooks ID and hash": {
      "main": [
        [],
        [
          {
            "node": "Slack: sync exceptions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": false
  },
  "tags": []
}
