GoHighLevel (GHL) API V2 - 無法根據外部 ERP ID 進行 POST/PUT 業務自訂欄位(401 及 422 錯誤)

嗨,各位,

我正在 n8n(雲端版本 2.22.6)中建立同步工作流程,以將銷售資料從我們的 ERP 更新到 GoHighLevel (GHL) 商業檔案(公司)

我的目標:

我們處理一個 CSV 檔案,其中包含按品牌分類的發票總額。我們需要使用儲存在 GHL 公司自訂欄位(business.id_gestionale)中的不可變外部 ERP ID(codice_cliente)在 GHL 中查找商業檔案,然後:

  1. 如果商業檔案存在: 透過 PUT 更新其財務自訂欄位。

  2. 如果商業檔案是新的: 透過 POST 建立新的商業檔案。

目前的工作流程設定:

  1. 觸發器 / 讀取檔案: 匯入包含 codice_clienteragione_socialetotale_ecopotstotale_lechuza 等欄位的 CSV。

  2. HTTP 請求(GET): 使用標頭驗證呼叫 GHL API V2(Authorization: Bearer pit-...Version: 2021-04-15)。

  3. If 節點: 檢查商業檔案是否被找到。

  4. HTTP 請求(POST / PUT): 標準 HTTP 節點以路由資料。

我們面臨的障礙:

問題 1:GET 查詢失敗(API V2 限制)

我們無法直接透過自訂欄位篩選 GET 請求。

  • 如果我們使用 https://services.leadconnectorhq.com/businesses/search?locationId=XXXX&query={{ $json.codice_cliente }},它會傳回空陣列 [],因為 GHL 全域查詢不會索引數值自訂欄位。

  • 如果我們嘗試在查詢字串中附加自訂欄位篩選(&customFields=[...]),GHL 會傳回 422 錯誤"property customFields should not exist"

  • 如果我們切換到全域擷取(/businesses/?limit=100),n8n 會拉取包含所有公司的巨大 1.2MB 有效載荷,這會覆蓋原始傳入 CSV 列的二進位 / JSON 上下文,使後續對應變得極其複雜。

問題 2:POST 授權迴圈(401 錯誤)

當記錄沿著 POST 路徑建立新公司(https://services.leadconnectorhq.com/businesses/)時,GHL 會傳回:

401 - {"message":"LocationId is not specified","error":"Unauthorized","statusCode":401}

即使 locationId 明確地在 JSON 本體中傳遞,且完全相同的 Bearer pit-... 令牌對 GET 請求和現有 ID 的 PUT 請求都運作完美。感覺就像 GHL 位置級 API 金鑰除非使用完整 OAuth Marketplace 應用程式,否則完全無法在 /businesses/ 端點上使用 POST

建議的替代方案(轉向連絡人 + 公司名稱):

由於使用標準 API 金鑰寫入 /businesses/ 端點似乎受到高度限制,我們正在評估轉向 Contacts 端點。

我們考慮改為建立 / 更新 Contacts,在 Contact 的自訂欄位內傳遞 ERP ID 和發票欄位,同時將聯絡人物件內的 companyName 欄位對應至商業檔案。

我對社群的問題:

  1. 在 n8n 中查詢 GHL 商業檔案使用 自訂欄位值的最佳實務是什麼,既不需擷取整個 1.2MB 資料庫也不會陷入 422 錯誤?

  2. 我們如何在 HTTP 請求節點之後繞過資料覆蓋行為,使得最終的 PUT / POST 節點仍然能夠存取原始 CSV 列資料(發票總額)?我們應該在檔案攝取之後立即使用新的 Compare Datasets / Merge (Enrich Existing Data) 節點嗎?

  3. 有人曾經使用標準位置 API 金鑰成功執行過 POST/businesses/,還是完整的 OAuth 整合是建立公司的必要條件?

  4. 關於我們的替代方案: 您認為切換至 Contacts 端點(同時在聯絡人內填充 companyName 欄位)是繞過商業檔案端點 API 限制的穩健、可靠的解決方案,還是有您建議的更好架構方案?

任何建議、解決方案或工作流程 JSON 片段都會非常感謝!

Dedi,這看起來像是兩個獨立的阻礙因素,不是一個 GHL bug:通過 codice_cliente 找到正確的公司,然後創建/更新它而不被 POST body 拒絕。如果該 ERP ID 只存在於 GHL 搜尋無法過濾的公司自訂欄位中,GHL 可能不是可靠的查詢來源;在你自己的資料中保持一個小小的 codice_cliente -> businessId 對應表,然後通過 businessId 更新。

在重新建構 Contacts 之前,執行一次安全檢查:使用相同的 token 和 locationId 發送最小的建立公司請求,但不包含 invoice/custom-field payload。它是否仍然返回 LocationId is not specified,還是只有在發送完整的 CSV 對應 body 時才返回?

這確實是一個文件完善的問題,你已經自己做了大部分的診斷工作。讓我逐一檢查每個問題。

關於GET查詢限制 最簡潔的解決方案是在工作流程開始時使用分頁一次性取得所有企業,然後使用Code節點按照你的自訂欄位值在記憶體中進行篩選。是的,這是一個大型載荷,但你只在每次工作流程執行時呼叫一次,而不是每個CSV列呼叫一次。類似這樣:

javascript

const allBusinesses = $input.all();
const target = allBusinesses.find(b => 
  b.json.customFields?.find(f => f.key === 'id_gestionale' && f.value === $('CSV Node').item.json.codice_cliente)
)

return target ?

關於在HTTP Request節點之後保留CSV資料 — 在你的GET呼叫之後使用設定為「Combine」模式的Merge節點。將原始CSV項目和HTTP回應都輸入到其中。這樣下游的PUT/POST節點仍然可以存取所有原始發票欄位以及GHL回應資料。這是這種確切情況的標準做法。

關於POST 401錯誤 — 你說得對,這是一個已確認的GHL限制。標準位置API金鑰(pit-...)沒有權限透過POST建立新的Business記錄。該端點需要完整的OAuth應用程式權杖或具有提升權限的Private Integration權杖。如果目前無法選擇完整OAuth,你轉向Contacts的做法實際上是最實用的前進道路。

關於Contacts樞軸 這是一個穩健的解決方案,使用廣泛。在聯絡人上填充companyName,GHL會自動將其關聯到相符的Business。在Contact自訂欄位中儲存你的codice_cliente和發票總額,你就能完全繞過Business端點限制。主要的權衡是你的財務資料存放在Contact層級而不是Business層級,這可能會根據你的GHL帳戶設定方式影響報告。

如果長期保留Business層級的資料很重要,正確的修復方案是設定具有OAuth的GHL Marketplace App,但這需要更大的投資,而Contacts方案會讓你今天就能解除封鎖。

你這裡有兩個獨立的錯誤,逐一修復會很有幫助,因為 401 和 422 代表完全不同的含義。

401 是驗證或範圍的問題。GHL API V2 權杖是按資源限定範圍的,寫入公司/企業自訂欄位需要特別具備企業寫入範圍,如果你的權杖是針對聯絡人設定的,很容易遺漏。確認權杖(或其背後的 OAuth 應用程式)確實具有企業(公司)寫入範圍,並且你使用的是 V2 基底 URL 搭配 GHL 要求的正確 Version 標頭,單單缺少或錯誤的 API 版本標頭就足以在 V2 上拋出 401。

422 是 payload 的問題。GHL 自訂欄位必須按其欄位 id 參照,而不是欄位名稱,而且自訂欄位的 body 結構很具體(是包含欄位 id 和值的物件陣列,而不是扁平鍵)。如果你按名稱或作為頂層屬性傳送 codice_cliente,那就是你的 422。先擷取公司自訂欄位定義以取得確切的欄位 id,然後使用該 id 來寫入。另外確認值的型別與欄位的型別相符,將字串傳送到數字欄位也會拋出 422。

所以:為 401 修復範圍和版本標頭,然後為 422 符合確切的自訂欄位 id 和 body 結構。當你執行它時,哪一個先出現,401 嗎?那必須先清除,422 才能夠達到。

[已解決] 使用 n8n 從 CSV 增量同步聯絡人銷售資料到 GoHighLevel(跨多個/重複聯絡人)

大家好!我想分享一個我們遇到的問題的解決方案,關於從 CSV 檔案增量更新年度品牌特定銷售資料到 GoHighLevel (GHL)。當您的 CRM 包含多個或重複聯絡人(例如,來自同一公司帳戶的不同員工或分公司經理)時,這特別有用。

經過多次測試後,我們成功建立了一個線性且具有復原力的工作流程,可以提前清潔地彙總資料,並在下游將其解包,以同時更新 GoHighLevel 中每個相符的重複聯絡人。

:world_map: 工作流程的功能及其運作方式

該工作流程的設計目的是自動化從會計或文件管理系統進行資料匯入。它計算按個別產品品牌細分的進度同比銷售指標,並動態更新「最後購買日期」。

以下是確切的逐步邏輯架構:

1. 資料提取和上游彙總 (CSV)

  • 復原力強的解析: 工作流程會擷取 CSV 檔案(透過 Google Drive 或手動觸發)。會計匯出通常會將資料欄位包裹在硬性雙引號內,這會破壞原生 CSV 解析器。第一個 JavaScript 節點會清潔十進位格式(處理逗號),並使用自訂行解析指令碼來重建乾淨的 8 欄資料物件。

  • 上游整合: 如果 CSV 包含同一檔案中同一客戶端的多份發票或交貨單據,程式碼會以程式方式將總額加總,為每個唯一商店 ID 產生單一累積記錄。這可防止 n8n 向 GHL 發送同一客戶端的非同步平行請求,避免 API 速率限制或競爭條件(其中操作會相互覆寫)。

2. CRM 上的聯絡人查詢 (GET)

  • 工作流程會將 GET 請求傳送到 GoHighLevel 的 /contacts/ 端點。為了繞過重複項目中公司電子郵件地址的差異或不一致,查詢會使用公司名稱(或唯一的外部商店 ID)進行搜尋。

  • GoHighLevel 會回應一個 JSON 承載,包含 contacts 陣列,列出所有符合該特定公司查詢的聯絡人

3. 邏輯分叉(合併和 IF)

  • 合併節點會將新鮮彙總的 CSV 資料與 GHL 的搜尋輸出配對。

  • IF 節點評估該公司是否已存在於 CRM 中(meta.total > 0)。如果客戶端是新的,工作流程會分支到 FALSE 路徑來建立新記錄(POST)。如果存在,它會沿著 TRUE 路徑進行更新。

4. 重複項目解包和線性化(拆分重複項目)

  • 這是處理重複記錄的秘訣所在。位於 IF 節點的 TRUE 輸出之後,一個專用的 JavaScript 節點隔離 GHL 返回的聯絡人陣列,並將其「展開」為 n8n 的獨立執行行

  • 例如,如果 GoHighLevel 中同一公司名稱下存在 3 個重複的聯絡人資料,此節點會立即將該單個執行執行緒轉換為3 個在下游平行流動的單獨項目

5. 增量計算(品牌數學)

  • 後續的 JavaScript 節點會逐個處理這些展開的聯絡人。它查看該特定聯絡人 ID 的自訂欄位內(例如,totale_ecopots_2026totale_lechuza_2026),提取當前歷史數值,並以數學方式加入從目前 CSV 檔案計算的新銷售量。它輸出一個乾淨、可立即儲存的承載。

6. 大量自訂欄位更新 (PUT)

  • 最終的 HTTP 請求節點執行指向動態 URL 字串的 PUT 命令:https://services.leadconnectorhq.com/contacts/{{ $json.id_contatto }}

  • 透過關閉節點進階設定內的 Execute Once 切換,n8n 執行完美的連續迴圈。它會為拆分程式碼產生的每個重複資料設定檔發送盡可能多的個別 PUT 要求,無縫地更新 CRM 中與該帳戶相關的每個單一代表資料設定檔的歷史指標。

感謝大家之前的指點。我希望這個結構佈局對任何處理重複資料庫資料設定檔上複雜增量計算的人有所幫助!