前言
Shopify App Store distribution 的 App 必須處理三類 mandatory compliance webhooks:顧客資料查閱、顧客資料刪除與商店資料刪除。第一版最容易完成的是 route、HMAC authentication 與 HTTP 200;但 200 只代表 Shopify 成功把請求交給 App,不代表資料已匯出、交付或刪除。
上一篇談的是 商家 UI 與 developer diagnostics 的責任分離。Compliance request 也需要相同觀念:Webhook transport、App 內處理、營運交付與稽核紀錄是不同責任,不能用一個綠色狀態全部概括。
這篇是工程案例與 2026-07-28 Shopify 官方文件的交叉整理,不是法律意見;若所在地法規、商家合約或 Shopify 後續規則更嚴格,應採更嚴格要求。
原本的做法與問題症狀
最初的三個 route 已經會:
- 接受 Shopify POST。
- 使用 framework helper 驗證 Webhook。
- 記錄 topic 與 shop。
- 回傳 200。
這能通過「endpoint 存在」的最低 transport 測試,卻留下三個實質問題:
customers/data_request沒有 durable record,回 200 後營運方甚至不知道還有待交付資料。customers/redact沒有從 local sync jobs/logs 找出相關 customer 或 order 資料。shop/redact沒有清除解除安裝商店的 session、connection 與同步狀態。
另一個看似省事的方向是收到 data request 就自動寄出 payload。這會把未驗證的 Email、敏感 JSON、公開下載連結與 mail delivery failure 全部帶進來。案例因此先採「App 內產生受限匯出+人工確認交付」。
問題是怎麼推敲出來的?
Git history 顯示功能按責任逐步補齊:先加 subscriptions/routes,再加入 local erasure、data request persistence、local export、delivery tracking,最後用 runbook 與 readiness gate明確標出外部交付責任。
現行 Shopify 文件也把三個 topic 分得很清楚:
customers/data_request提供 App 需要交給 store owner 的 customer/order resource IDs。customers/redact要刪除對應顧客資料。若顧客過去六個月沒有下單,Shopify 在刪除請求十天後送出;有近期訂單時,請求會延後到六個月期滿。shop/redact在商店擁有者解除安裝 App 48 小時後送出,要求清除該 shop 資料。
時間點是 Shopify 何時送 delivery,不等於 App 可以再等待相同天數才開始處理。App 仍要定義內部 SLA、稽核與 escalation。
最後採用的架構
案例把三條流程拆開:
Shopify compliance webhook
→ HMAC authentication
→ topic-specific local action
→ HTTP 200
customers/data_request
→ upsert pending request
→ generate local-only export
→ identity / scope review
→ secure external delivery
→ delivered timestamp + note
customers/redact
→ parse customer / order identifiers
→ delete matching same-shop sync jobs and logs
shop/redact
→ delete same-shop sessions, connection and sync state
Data request export 只包含 App 自己保存的資料,不重新向 Shopify 或外部資料系統抓取,也不冒充商家的完整資料副本。交付前要確認請求人、範圍與安全通道;不能把 payload 裡的 Email 當成已驗證收件人。
關鍵實作
Route 先使用 Shopify template 的 authentication helper:
export async function action({ request }: ActionArgs) {
const { payload, shop } = await authenticate.webhook(request);
return handleComplianceWebhook({
shop,
topic: 'customers/data_request',
payload,
});
}
Data request 有平台 request ID 時,以 shop+topic+request ID upsert,避免同一請求重送後建立多筆待辦:
await complianceRequests.upsert({
key: { shop, topic, platformRequestId },
create: { status: 'pending', identifiers, payloadSnapshot },
update: {
status: 'pending',
identifiers,
payloadSnapshot,
export: null,
deliveredAt: null,
},
});
交付狀態不能跳過 export:
if (!request.completedAt || !request.exportJson) {
throw new Error('請先產生資料匯出');
}
await markDelivered({
requestId: request.id,
deliveredAt: new Date(),
note: safeDeliveryNote,
});
Customer redaction 依 resource IDs 與必要 tokens 比對同 shop rows;Shop redaction 則以 shop 為 boundary 刪除多類 local rows。公開文章不放真實 payload、Email、Phone、order ID、shop domain 或 delivery note。
實際驗證
來源 repository 的測試可證明:
- 三個 route 都先執行 Webhook authentication,再交給 shared handler。
- 三個 topic 都有對應 subscription 與 route。
- Data request 會保存 pending record,以及 customer/order identifiers。
- 同 shop、同 topic、同 request ID 重送時更新同一筆,不建立重複 request。
- Data request 不會誤刪同步資料。
- Export 只包含同 shop、符合 identifiers 的 local sync jobs/logs。
- 未產生 export 時不能標記 delivered。
- 另一個 shop 不能匯出或標記本 shop 的 request。
- Customer redaction 只刪除同 shop 的 matching rows。
- Shop redaction 的測試會保留其他 shop 資料。
這些仍是 repository 內測試,不是正式 Shopify delivery rehearsal。案例沒有可公開的 Dev Dashboard delivery log、長時間 retention test 或外部交付演練結果。
仍然存在的限制
第一,shared handler 在回 200 前同步執行資料庫 upsert、掃描與 delete。Shopify 的 HTTPS Webhook 整體 request timeout 是五秒;資料量變大時,customer redaction 的逐列比對可能超時。更穩健的方向是先持久化帶有 delivery ID 的 compliance job,再快速 ACK,由受控 worker 完成可重試處理。
第二,案例以 platform data request ID 去重 customers/data_request,但沒有保存 X-Shopify-Webhook-Id。customers/redact 與 shop/redact 多半可重入,仍應保存 delivery ID、attempt 與結果,才能回答某次 delivery 是否完成。
第三,customer matching 會在 JSON snapshot 中找 identifiers。這能覆蓋早期未正規化資料,卻可能因格式差異漏掉,或因字串碰巧相同而刪錯。長期應把 customer/order identity 正規化成可索引欄位,並做 deletion inventory。
第四,shop redaction 目前明確刪除 session、connection、sync settings/jobs/logs 與 AI mapping setting;僅靠這段函式與文件還不能證明所有 schema tables、backup、log sink、cache 與第三方 processor 都已清除。正式上線需要 data inventory、foreign-key/cascade review、backup retention policy 與 integration test。
第五,delivered 是人工紀錄,不是加密檔案傳輸的技術證明。若未來加入 Email 或下載連結,還要處理身份驗證、有效期限、access log、bounce、重試與撤銷。
最後,Webhook payload 有 API version。Shopify 建議每季更新到 latest stable 並在更新前測試 handler;不能設定一次 compliance topics 就假設 payload 永遠不變。
可以延伸到哪些情境?
Privacy request 最重要的是把狀態拆清楚:
received → authenticated → persisted → processing
→ completed → delivered / redacted → audited
這套狀態也適用 ERP、CRM、會員中心與資料倉儲。每個 processor 都要能回答保存了哪些資料、如何找出、誰能交付、如何刪除、如何處理重送,以及哪一項仍需要人工或法律確認。
本批上線治理文章到這裡完成:Readiness CLI 阻擋可自動判斷的缺口、商家 App Home 只顯示可操作狀態,而 compliance workflow 則把收到、完成與交付拆成可追蹤步驟。
參考資料
- Shopify:Privacy law compliance
- Shopify:Verify webhook deliveries
- Shopify:Manage webhook subscriptions
- Shopify:Troubleshoot webhooks
- Shopify:Work with protected customer data
以上平台文件查核日期:2026-07-28。
