Skip to Content
核心概念外部回呼External callbacks

External callback 與 token lifecycle

Callback 讓外部系統在沒有使用者 session 的情況下,對一張指定的自訂資料表執行 create 或 update。Token 綁定資料表、允許操作與 minting moderator;寫入仍會通過 ACL actor、rules、unique constraints、invariant、history 與 triggers,不是繞過業務契約的後門。

生命週期:mint → 保存一次性 secret → POST → revoke

1. Moderator mint token

curl -X POST \ "$BASE_URL/private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/tables/22222222-2222-4222-8222-222222222222/callback-tokens" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "ERP 訂單匯入", "allowed_ops": "create,update", "valid_until": "2026-12-31T23:59:59" }'

allowed_ops 只接受兩個 literal:

  • "create":只能新增,最小權限的預設選擇。
  • "create,update":可新增及更新。沒有 update-only token。

回應為 201,包含 URL 使用的 token_id 與一次性 secret

{ "token_id": "33333333-3333-4333-8333-333333333333", "secret": "cbsec_example_only_0000000000000000", "table_id": "22222222-2222-4222-8222-222222222222", "allowed_ops": "create,update", "valid_until": "2026-12-31T23:59:59" }

2. 立即保存 secret

Secret 只在 mint response 出現一次。把它寫入整合服務的 secret manager,不要記錄到 application logs、analytics、ticket 或瀏覽器 storage。之後 callbackTokens.list 只回 metadata,不會再次顯示 secret;遺失時應 revoke 並 mint 新 token。

3. External POST

Bearer 模式:

curl -X POST \ "$BASE_URL/public/module/custom_tables/callback/33333333-3333-4333-8333-333333333333" \ -H "Authorization: Bearer $CALLBACK_SECRET" \ -H "Content-Type: application/json" \ -d '{ "op": "create", "data": { "訂單編號": "ORD-1042", "數量": 3 }, "idempotency_key": "erp-event-1042" }'

也可用 replay-protected signature:X-Callback-Timestamp 是 Unix seconds(允許正負 300 秒),X-Callback-Signature 是以 sha256(secret) 的 32 raw bytes 作 key,對 "{timestamp}.{raw body}" 計算的 hex HMAC-SHA256。簽章必須使用實際送出的 raw bytes,不能重新序列化 JSON 後再簽。

Create 的 data 可用顯示名稱或 internal keys;update 必須帶 record_id,且 data 只接受 internal keys:

{ "op": "update", "record_id": "44444444-4444-4444-8444-444444444444", "data": { "col_aaaaaaaa_aaaa_4aaa_8aaa_aaaaaaaaaaaa": 5 }, "idempotency_key": "erp-event-1043" }

idempotency_key 會在同一 token 下保留 24 小時;相同 key 回傳原始 accepted result 並設 replayed: true,不再次寫入。每個外部 event 都應提供穩定且唯一的 key。

4. Revoke

curl -X DELETE \ "$BASE_URL/private/module/custom_tables/chatroom/11111111-1111-4111-8111-111111111111/tables/22222222-2222-4222-8222-222222222222/callback-tokens/33333333-3333-4333-8333-333333333333" \ -H "Authorization: Bearer $TOKEN"

輪替時先 mint 並部署新 credential,確認新 token 有成功 request,再 revoke 舊 token。未知、已撤銷、已過期與 auth 失敗一律對 public caller 回 404 Not found,避免 token enumeration;client 不應根據 404 猜測是哪一種原因。

Mint、list、revoke 的完整 contract 分別見 callbackTokens.mintcallbackTokens.listcallbackTokens.revoke

Accepted response 與 approval

一般成功的 statuscreatedupdated,並回傳 record_id。若 require_approval 命中,public callback 刻意不回 private API 的 409,而是回 2xx:

  • held_for_approval:已建立 staged change;process_idstaged_change_idrule_label 有值,record_idnull。這是 terminal success,不要重送。
  • already_pending:相同目標或 natural key 已在等待;沿用原本 process,不建立第二筆。
{ "ok": true, "op": "create", "status": "held_for_approval", "record_id": null, "process_id": "55555555-5555-4555-8555-555555555555", "staged_change_id": "66666666-6666-4666-8666-666666666666", "rule_label": "高額訂單變更", "replayed": false }

一定要用 status discriminator,而不是只看 HTTP 2xx。Approval 後的套用仍會重新通過當時有效的規則與 invariant。

Body、rate 與 lock 行為

Status契約Client 行為
400 / 409Shape、rule、unique 或 transition 問題修正 payload 或業務衝突;不要盲目重試
404Token/auth 的 uniform response停止並由 operator 檢查 token lifecycle
413Raw request body 超過 65,536 bytes縮小 request;不可拆成同一 event 的無序重送
423Table 正在 migration 等 lock使用相同 idempotency key 指數退避後重試
429每 token 每分鐘超過 60 requests等下一個 rate window,加入 jitter,維持同一 idempotency key
429 structured detailApproval pending bucket 已滿暫停 producer,讓 reviewer 消化 queue;這不是短暫 rate window

Body cap 按實際 bytes 計算,不是 JSON 字元數。Rate limit 是 token 共用額度;不要靠多 mint tokens 規避節流,應做 producer backpressure。已知的 4xx / 423 / 429 會允許修正後沿用 key 重試;ambiguous 5xx 可能發生在 commit 之後,client 必須保留同一 key,避免重複資料。

Warning Public callback 沒有 acting chatroom。Enforcing channel table 會拒絕沒有 room binding 的 callback 寫入;請改用具 acting-room contract 的 private integration。invariant 則刻意套用於 callback。

Public endpoint 的精確 response 與 errors 見 callback.write。完整端到端流程見自動化指南,或用流程精靈練習 private 設定步驟。

讓公開表單自己長出欄位

透過 callback token 送資料的公開表單,可以直接問 API「我能寫哪些欄位」,不必把欄位清單寫死,然後每次有人改表就過期:

curl "$BASE_URL/public/module/custom_tables/callback/33333333-3333-4333-8333-333333333333/form-schema" \ -H "Authorization: Bearer $CALLBACK_SECRET"

回應是資料表的顯示名稱,加上可寫入欄位的 {name, type}rolluplookupformula 不會出現,因為它們本來就寫不進去;linkattachmentjsonusersocial_client 會列出來,因為表單確實可以送這些值。

驗證方式與寫入路由完全一致,只有一個細節不同:GET 沒有 body,所以 HMAC 那條路徑簽的是 "{timestamp}."(空 body),金鑰是 sha256(secret) 的 32 個原始位元組,時間窗同樣是 ±300 秒。不存在、已撤銷、已過期的權杖,以及任何驗證失敗,都回和寫入路由一樣的統一 404

在拿它來開發前,還有兩個和寫入路由不同的地方值得知道:這個路由不看權杖的 allowed_ops,所以只能 create 的權杖同樣能取得表格描述;而它沒有流量限制,所以永遠不會回 429

反方向、也就是把資料唯讀公開給匿名讀者,請見公開讀取

Last updated on