n8n + Notion 自動化實戰:把客戶、訂單、內容排程同步成單一真相來源
Notion 是好用的工作台但不是 CRM;用 n8n 把 Stripe 訂單、HubSpot 客戶、Postiz 內容排程同步進來,就變成中小企業的 single source of truth。本文用三條真實 pipeline 拆解 schema 設計、rate limit、雙向同步衝突解決,附 10 個 production blueprint。
中小企業最常見的悲劇:客戶資料在 HubSpot、訂單在 Stripe Dashboard、行銷內容在 Postiz、研究筆記在 Google Docs、合作會議記錄在 Slack。老闆問一句「上個月實際成交幾單,誰買的,哪些客戶 onboarding 卡住」,整個團隊翻五個工具拼三天,最後拿出來的數字還對不上。
這不是工具不夠多,是沒有 single source of truth。本文示範用 n8n 把三條主流資料管線都同步到 Notion,讓 Notion 變成「人看得懂、機器寫得進去、互相 linkable」的單一真相來源。三條 pipeline、10 個 blueprint、30 天落地節奏,全部照抄。
為什麼中小企業需要 single source of truth,而 Notion 為什麼適合(也適合不到哪裡)
先把問題講清楚:你需要的不是「另一個 CRM」,是「一個地方能看到所有客戶、訂單、內容的當前狀態」。
真實場景:5 個工具開的 5 個 truth
上個月幫一家 30 人規模的 SaaS 客戶做諮詢,他們的資料地圖是這樣:
- HubSpot:客戶 contacts + deal pipeline,但沒接訂單
- Stripe:訂單 + 訂閱狀態 + MRR,但沒寫客戶筆記
- Notion:產品文件 + 客戶 onboarding checklist,但訂單狀態靠人工貼
- Postiz:社群排程 + 文章發布,但效果數字回填靠人工
- Slack:所有的「老闆問了沒人答」歷史紀錄
CSM 想知道「這個客戶有沒有續訂」要切到 Stripe,想知道「這個客戶 onboarding 卡哪」要切到 Notion,想知道「上次跟他通信什麼內容」要切到 Slack。每個動作切三個工具,30 個客戶就是 90 次切換,一天就是 4 小時純粹在找資料。
最後決議:所有「需要被人查的真相」全部同步進 Notion,原始系統照常運作,Notion 變成「總前台」。三個月後客戶查資料時間降了七成,老闆問月底 MRR 五秒就有答案。
Notion 適合做 SoT 的 3 個理由
第一,Notion 的 relation + rollup 已經夠像關聯式資料庫。一個 Customer page 透過 relation 連到多個 Order page,rollup 算出 LTV、最近訂購日、active subscription 數量,這套等同 SQL 的 LEFT JOIN + SUM,但完全不用寫 query。
第二,Notion 的權限模型適合中小企業。你可以把整個 Customer database 給銷售看,但 password / API key 那一欄只給工程師看;客戶要看自己的 onboarding 進度,給他單一 page 的 share link 就行。權限細到 property 等級,比大多數 SaaS 強。
第三,Notion 的人機介面友好。CRM 工具長得像 1998 年的 ERP 後台,新員工要學三天;Notion 像在用 Google Docs,新人 30 分鐘就上手。資料能被人看懂,才能被人用。
不適合的 2 個硬限制
第一,Notion API rate limit 是 3 requests/second/integration,遠低於正規後端。Stripe 大促一天 5000 筆訂單同步進來,沒做佇列就會撞限流,事件丟失。所以 Notion 不能做 transaction 系統,只能做 mirror。
第二,Notion 沒有真正的 unique constraint 與 foreign key。relation 是軟的(會接受空連結、會留 orphan 記錄)、property 沒有真正的 unique index、duplicate page 隨時可能被人手動建。production 同步必須在 n8n 那層做完整 idempotency 設計。
理解這兩個限制後,正確姿勢就清楚了:原始系統(HubSpot、Stripe、Postiz)是 SoT,Notion 是 SoT 的 human-readable mirror,n8n 是兩邊之間的 sync engine。
pipeline 1:客戶資料同步(HubSpot / Stripe Customer → Notion CRM database)
第一條 pipeline 永遠先做客戶同步。原因是訂單與內容都需要關聯到客戶,客戶沒進來其他都白做。
Notion CRM database schema 設計
開一個叫 Customers 的 Notion database,最小可用 schema 長這樣:
| Property | Type | 來源 | 備註 |
|---|---|---|---|
| Name | Title | HubSpot company name | 主要顯示 |
| External ID | Rich Text | HubSpot vid 或 Stripe customer id | 同步 idempotency 用,必填、必加 unique 標記 |
| HubSpot | 給 CSM 直接 mailto | ||
| Stage | Select | HubSpot deal stage | 對應 sales pipeline |
| LTV | Number | rollup from Orders | Order relation 算出 |
| Active Subscription | Number | rollup from Orders | 同上 |
| Last Touch | Date | n8n 每次 sync 時寫 | 判斷哪些客戶超過 30 天沒互動 |
| Owner | Person | 手動 | Notion 內部分配 |
| Notes | Page Content | 手動 | CSM 寫客戶筆記 |
| Source System | Select | HubSpot / Stripe / Manual | 知道誰是真相 |
重點在 External ID 這一欄。Notion 沒有 unique constraint,所以同步邏輯不能寫成「找名字一樣的就 update」(會誤觸到同名客戶),必須是「先用 External ID 查 page,找到 → PATCH;找不到 → CREATE」。
關於要不要把 Stripe customer id 和 HubSpot vid 分兩欄:建議合併成單一 External ID + 加 Source System,理由是後續 rollup / filter 邏輯會單純很多。如果客戶橫跨兩個系統(HubSpot 有 + Stripe 也有),建立兩筆 Customer page、用 relation 互指,比強迫合併乾淨。
n8n workflow 結構:Schedule Trigger + 雙向 upsert
工作流骨架:
Schedule Trigger (每 15 分鐘)
↓
HubSpot:搜尋上次同步後 updatedAt > last_sync 的 contacts
↓
SplitInBatches (batch size 10,因為 Notion 3 req/sec)
↓
Function:把 HubSpot fields 映射成 Notion property payload
↓
Notion:用 External ID 查找 page
├─ 找到 → PATCH (update properties)
└─ 找不到 → POST (create page)
↓
PostgreSQL:寫 sync log(external_id, action, timestamp, payload hash)
↓
Wait 350ms(保留 buffer,避免併發跨 batch 撞限流)
↓
迴圈下一個 batch
雙向同步要不要做?建議第一階段先做單向(HubSpot → Notion),等穩定 60 小時再加反向。反向的觸發點是「CSM 在 Notion 改 Owner / Stage」,用 Notion webhook(透過 Make 的中繼或 Notion automation)打回 n8n,再寫進 HubSpot。
Notion API rate limit 的批次與佇列策略
3 req/sec 不是嚴格節流,是「超過會回 429 + Retry-After header」。production 經驗:
- batch size 10:每 batch 跑完 wait 350ms,剛好踩在 ~2.8 req/sec 邊緣,留 buffer
- 每天初始全量 sync:開機後 5 分鐘內全量比對,補上昨晚漏的;之後每 15 分鐘 incremental
- 429 後 exponential backoff:第一次撞 1 秒、第二次 2 秒、第三次 4 秒,超過 8 秒直接寫 DLQ 表
- Notion integration 拆兩個:一個給寫入(這條 pipeline 用),一個給讀取(dashboard、查詢用),兩個 token rate limit 是分開計算的
連 retry 機制都還沒設計過?先把 n8n workflow 錯誤處理三層模式 那套接上來,再來談批次。
pipeline 2:訂單同步(Stripe webhook → Notion Order database)
訂單同步比客戶複雜,因為訂單會被退款、會被續訂、會被失敗,狀態流不是單向。
webhook → n8n → Notion 的 idempotency
Stripe webhook 一個事件 72 小時內最多重送 16 次,沒做去重等於同一筆訂單在 Notion 出現 16 條。前置處理跟 Stripe webhook 多步驟分支設計 一樣:簽章驗證 → event_id 去重 → 立刻回 200 → 入 queue 處理。
進到 Notion 那段是這樣:
Queue Worker
↓
Stripe event:拿 charge.succeeded / invoice.payment_succeeded
↓
Function:抽 customer_id + amount + currency + 訂閱資訊
↓
Notion:用 customer_id 查 Customer page(pipeline 1 已建立)
↓
Notion:用 charge_id 查 Order page
├─ 找到 → PATCH(更新狀態)
└─ 找不到 → POST(建立 Order page,relation 到 Customer)
關鍵:用 charge_id 而不是 invoice_id 當 External ID。一個 invoice 可能對應多次重試付款(每次有自己的 charge_id),用 invoice_id 會把第二次成功的付款蓋掉第一次失敗的紀錄。
Notion relation field 把 Order 關聯回 Customer 的兩種方法
方法 A:n8n 在建立 Order 時,先 query Customer database 拿到 page_id,寫進 Order 的 relation property。優點是同步當下就有 relation;缺點是每筆 Order 多一次 API call。
方法 B:先用 External ID 純文字寫進 Order,每天跑一次 batch 工作流補 relation(找 Order.customer_external_id 比對 Customer.External ID)。優點是同步本身快;缺點是 Order 進來後到 batch 跑完之間有空窗。
production 經驗:60 單以下的客戶用 A、60 單以上的用 B。原因是 A 的 API call 量會跟著訂單規模線性放大,B 是固定成本。
退款 / 訂閱續訂 / 失敗事件的狀態欄位設計
Order database 必有的狀態欄位:
| Property | Type | 來源 |
|---|---|---|
| Status | Select | succeeded / refunded / failed / disputed / partial_refunded |
| Refund Amount | Number | charge.refunded 事件的 amount_refunded |
| Subscription Phase | Select | initial / renewal / final / cancelled |
| Failed Reason | Rich Text | failure_message |
| Dispute Reason | Rich Text | dispute 事件的 reason |
Status 用 Select 而不是 Checkbox 的原因:狀態不是布林,是有限狀態機。partial_refunded 跟 disputed 是兩種完全不同的 case,要分開追蹤。
退款不要修改原訂單的 amount,新增一條 Refund Amount,因為原始交易金額是會計憑證,不能被「修改」,只能被「沖銷」。會計部門看到原 amount 改了會抓狂。
pipeline 3:內容排程同步(Postiz / GA4 → Notion Content Calendar)
第三條把 Notion 升級成內容飛輪儀表板,把產出、排程、成效拉在一張表。
一張 Notion Content Calendar 同時管文章、社群、Lead Magnet
Calendar database 的 schema:
| Property | Type | 來源 |
|---|---|---|
| Title | Title | 手動 |
| Type | Select | Article / FB Post / IG Carousel / Threads / Lead Magnet |
| Pillar | Select | 對應品牌的 content pillar |
| Status | Select | planned / drafting / scheduled / published / measured |
| Publish Date | Date | n8n 從 Postiz 同步 |
| URL | URL | 文章發布後 n8n 寫回 |
| Pageviews | Number | GA4 14 天後回填 |
| Engagement Rate | Number | Postiz analytics 14 天後回填 |
| CTR | Number | GSC 14 天後回填 |
| Related Customer | Relation | 連到 Customer database(如果這篇是寫給某個客戶問題) |
| Lead Magnet Conversion | Number | D1 / GA4 conversion event |
Type + Pillar 是兩個維度,不要混。Type 是「格式」(FB / IG / Article),Pillar 是「主題分類」(自動化教學 / 行業案例 / 工具評測),用兩個 select 才能做交叉分析。
GA4 表現回填到 Notion
GA4 Data API 拿數據的 n8n workflow:
Schedule Trigger (每天 09:00)
↓
Notion:查 Calendar 中 Status=published 且 Publish Date >= 14 天前的記錄
↓
SplitInBatches (size 5)
↓
GA4:用 path 查該文章 14 天內的 pageviews + bounceRate + avgEngagementTime
↓
Notion:PATCH 該 page,寫回 Pageviews / Engagement
↓
Function:判斷 Status 是否要從 published 轉成 measured
↓
Notion:PATCH Status
回填一定要等 14 天以上,因為前 7 天的 GA4 數據還在採樣 + reprocessing。回填太早數字會跳,後面再修又會被同事覺得「這數字不可靠」,整套 SoT 信用就崩了。
內容狀態流:planned → drafting → scheduled → published → measured
五段狀態的判斷規則:
planned:Calendar 有條目,但沒人開始寫drafting:Notion page 內容字數 > 100(或人工標記)scheduled:Postiz / Astro repo 已建立 post,n8n 自動更新published:Postiz status=published 或文章 URL 200,n8n 自動更新measured:發布 14 天後,GA4 數據已回填,n8n 自動更新
measured 是篩出「可以做覆盤」的條目用的,每月底跑覆盤就 filter Status=measured AND Publish Date >= 上月 1 號,自動拿到上個月所有可以分析的內容。
雙向同步的衝突解決:哪邊是 source of truth
雙向同步是 production 最大地雷。資料工程的鐵則:每一個 entity 永遠只能有一個 SoT,其他都是 mirror。
timestamp-based last-write-wins vs 欄位級分權
簡單做法:每筆記錄存 last_modified_at + last_modified_source,誰晚改誰贏。問題是當 HubSpot 和 Notion 同時改同一筆,兩邊都覺得自己是後改的(時鐘漂移、時區差),會打架。
production 解法:欄位級分權。明確定義哪些欄位由誰當 SoT,超出範圍直接 reject:
| 欄位 | SoT | 反向同步 |
|---|---|---|
| Customer.Name / Email | HubSpot | Notion 不可改 |
| Customer.Stage | HubSpot | Notion 改了要送回 HubSpot |
| Customer.Notes | Notion | 不送回 HubSpot |
| Customer.Owner | Notion | 送回 HubSpot |
| Order.amount / status | Stripe | Notion 唯讀 |
| Order.Internal Note | Notion | 不送回 Stripe |
寫死規則比 timestamp 邏輯穩 10 倍。
deletion 同步是地雷 — 用 archive 取代 delete
不要把「Notion 刪 page」翻譯成「HubSpot 刪 contact」。原因:
- 員工誤刪 Notion page 太常見
- HubSpot / Stripe 刪除是 GDPR 等級操作,不可逆
- 客戶資料一旦誤刪,連 backup 復原都得跑 24 小時流程
正確姿勢:所有 Customer / Order 加 Archived checkbox,要「刪除」就打勾。每天跑一次 cleanup workflow 把 Archived=true AND archived_at > 30 天 的記錄 hard delete。30 天是緩衝期,給人類反悔。
衝突 log 寫進 Notion 一個獨立 database
開一個 Sync Conflicts database,每次 n8n 偵測到「兩邊都改了同一欄位」就寫一筆:
| Property | 內容 |
|---|---|
| Entity | Customer / Order 的 page link |
| Field | 衝突欄位名 |
| HubSpot Value | 來源 1 的值 |
| Notion Value | 來源 2 的值 |
| Detected At | 時間 |
| Resolved By | 解決衝突的人 |
| Resolution | 採用哪邊 |
每週開會花 15 分鐘掃這張表,找出系統設計缺漏。production 跑半年內,衝突數應該從每週 30 筆掉到每週 < 3 筆,掉不下來代表規則設計還有問題。
10 個 production blueprint:照抄就能用
把上述三條 pipeline 變成可重用 blueprint,每個都已經在 n8nstart.cc 模板商城上架,購買後直接 import。
- 新訂單自動建立 onboarding task 給 CSM:Stripe
checkout.session.completed→ 建立 Notion Task page,relation 到 Customer,分配給 Owner - 訂閱到期前 7 天自動寫客戶筆記 + Slack alert:Stripe Subscription 到期日 -7 天觸發 → Notion Customer page 加一條 Note → Slack DM 給 Owner
- HubSpot deal stage 變動同步:HubSpot webhook → Notion Customer.Stage 更新 → 內部 Slack 通知 sales lead
- 退款超過 NTD 10000 自動建立 escalation:Stripe
charge.refunded金額 >10000 → 建立 Notion Escalation page → email 給財務 + Slack alert - 內容發布後 14 天 GA4 回填 + 自動歸檔:上面 pipeline 3 的完整版
- Lead Magnet 下載觸發 nurture sequence:D1 magnet_leads INSERT → Notion 建立 Lead page → 啟動 7 天 email 序列 + 5 天後 sales follow-up task
- 客戶 30 天未互動自動 churn risk 標記:每天 cron → Notion filter
Last Touch < 30 天前→ 加 ChurnRisk tag → 推到 CSM weekly review - 新文章發布自動同步到 Postiz 排程:Astro repo push → n8n 抓 frontmatter → Postiz 建立 FB/IG/Threads 排程 → 寫回 Notion Content Calendar
- 競品內容變動 alert:Notion 競品清單 → daily cron 抓對方 RSS → 有新文章 PATCH 進 Notion + Slack
- 月底 MRR 報表自動產出:Stripe 月底跑 → 算 MRR/Churn/Expansion → 寫進 Notion Monthly Report page + 寄給管理層
每個 blueprint 都遵守一條原則:Notion 是 human-readable mirror,原始系統才是 SoT。寫入 Notion 隨便寫,反向寫回原始系統嚴格驗證。
30 天落地節奏
從零開始把這套 SoT 鋪起來的節奏:
| 日 | 任務 | 完成標準 |
|---|---|---|
| D+0 | 開 Notion workspace + 3 個 database(Customer / Order / Calendar)+ Schema 鎖死 | property 全部建好 + External ID 設成 unique view |
| D+3 | n8n self-host + Notion integration token + 基礎 workflow 框架 | Schedule Trigger 跑 1 筆測試 → Notion 出現一筆記錄 |
| D+7 | Pipeline 1 客戶同步上線 + 跑 60 小時穩定 | HubSpot 100 筆 contacts 全部進 Notion,零 duplicate |
| D+14 | Pipeline 2 訂單同步上線 + 完成 webhook 驗簽 + 去重 | Stripe test event 重送 16 次,Notion 只有 1 筆 |
| D+21 | Pipeline 3 內容排程同步上線 + GA4 回填 | Calendar 有上週發布的內容自動標 measured |
| D+28 | 衝突 log database + cleanup workflow + Archive 機制 | 故意製造衝突,Sync Conflicts database 有紀錄 |
| D+30 | dashboard 化:在 Notion 建 5 個 view 給不同角色(CSM/Sales/Marketing/Boss) | 每個角色 30 秒內找到自己要的數字 |
收斂三個指標:
- 同步成功率 > 99.5%(DLQ 進場率 < 0.5%)
- 衝突率 < 1%(每 100 筆同步 < 1 次衝突)
- 老闆查月 MRR 時間 < 10 秒
三個指標達標,Notion 就真的變成 SoT。沒達標代表設計還有缺,回頭看上面對應段落。
常見問題
Q:Notion API rate limit 3 req/sec,公司一天 1 萬筆訂單會卡死,怎麼辦? 答:先做佇列(Redis-backed queue),再把 batch size 拉小 + 不要併發。10000 筆 / 3 req/sec ≈ 56 分鐘,跑得完。如果業務真的需要 < 1 分鐘 latency,Notion 不是對的工具,改用 Retool 或自建 dashboard。
Q:Notion sync 到一半被人手動改了,會被 n8n 蓋掉嗎? 答:取決於同步邏輯。簡單版的 PATCH 會直接蓋;正確做法是先 GET 比對 hash,沒變才寫;變了就觸發衝突 log。production 工作流必須有這層 guard,不然 CSM 的筆記隨時被覆蓋。
Q:Notion 有沒有自家的「自動化」可以取代 n8n? 答:Notion automation 適合 Notion 內部觸發(property 變了 → 改另一個 property),但跨系統整合(Stripe webhook → Notion)能力很弱,連 webhook signature 驗證都沒有。n8n 是補這塊的最佳工具。實際組合:n8n 跨系統 + Notion automation 內部觸發。
Q:我們已經用 Airtable 一段時間了,要不要全切換到 Notion? 答:不一定。Airtable 跑大量結構化資料(萬筆以上)效能比 Notion 好;Notion 強在「人看的介面 + 內嵌文件」。建議混用:交易型資料留 Airtable / Postgres,給人看的儀表板放 Notion,n8n 在中間同步。SoT 的概念跟工具無關。
把這套 SoT 鋪好後最大的改變不是省時間,是「老闆問問題有單一答案」。中小企業最痛的不是缺工具,是工具太多沒對齊。n8n + Notion 不會讓你變大公司,但會讓你的小團隊跑得像一個整體。