Skip to Content
API 參考身分驗證

身分驗證

InfraIO Pay 有兩個 API 介面,各自採用不同的驗證模型。請依照 呼叫者身份選擇:

介面路徑前綴對象驗證
商家 B2B/b2b/v1/*你的伺服器HMAC-SHA256 請求簽章
儀表板依服務劃分:/auth/*/payment/*/merchant/*/event/*/user/*商家儀表板的瀏覽器 sessionBearer JWT

本頁介紹 B2B 介面 — 你從伺服器以 API 金鑰組呼叫的那個。若你 要嵌入 InfraIO 儀表板或打造內部工具,請使用儀表板介面(獨立的文件, 尚未公開)。

gateway 依前導前綴為每個介面路由,並在轉發前 剝除 該前綴: /b2b/v1/checkout-sessions/quick 抵達 payment-service 時是 /v1/checkout-sessions/quick,而儀表板的 /payment/v1/orders 抵達時是 /v1/orders。所以若你在別處看到光禿禿的 /v1/* 路徑,那是公開前綴 被移除之後的 後端內部 路徑 — 你的用戶端始終送出帶前綴的形式。(對 簽章的一個影響:B2B canonical 字串簽章的路徑 仍帶著 /b2b 前綴 — 見下文。)

Endpoints

環境Base URL
Testhttps://api-dev.infraio.xyz
Livehttps://api.infraio.xyz

URL 模式相同 — 環境是由金鑰前綴(pk_test_… vs pk_live_…) 控制,而非 URL。

金鑰組

你會從商家儀表板取得兩個值(Developers → API keys → + Add key):

  • Publishable key(pk_test_…pk_live_…)— 識別你的帳號。 以 X-Client-ID 傳遞。可以安全地嵌入瀏覽器 bundle(SDK 已經這樣做)。
  • Secret key(sk_test_…sk_live_…)— HMAC 簽章金鑰。 僅限伺服端使用。視同資料庫密碼般保護。

若 secret key 不慎流入瀏覽器 bundle、git repo、log 或聊天室 — 立刻從儀表板撤銷它。沒有重疊視窗;撤銷是即時的。請發行新金鑰 並重新部署。

簽章請求

每個對 /b2b/v1/* 的呼叫都會帶三個標頭:

X-Client-ID: pk_live_70d0becbd7a53047b0dfe88a960d10ad X-Timestamp: 1715990400 X-Signature: 9a8b7c6d… (hex HMAC-SHA256)

簽章是針對 canonical string 計算的:

METHOD + "\n" + PATH + "\n" + TIMESTAMP + "\n" + BODY
  • METHOD — 大寫的 HTTP verb(POSTGET 等)。
  • PATH — 請求路徑/b2b 前綴,不含 host 且不含 query string (例如 /b2b/v1/checkout-sessions/quick)。Gateway 在剝除 /b2b 之前就會以原始進來的路徑驗章,因此前綴必須存在。Query 參數 參與簽章 — 對於 GET …?cursor=…&limit=20,只簽路徑,不要 簽 ?… 部分。
  • TIMESTAMP — Unix 秒,以十進位字串表達(例如 "1715990400"), 須與 X-Timestamp 完全一致。
  • BODY — 原始請求 body 位元組。GET/DELETE 為空字串。

secret key 做 HMAC-SHA256,輸出 hex:

import { createHmac } from "node:crypto"; function sign({ method, path, body, secret }: { method: string; path: string; body: string; secret: string; }) { const timestamp = Math.floor(Date.now() / 1000).toString(); const input = [method.toUpperCase(), path, timestamp, body].join("\n"); const signature = createHmac("sha256", secret).update(input).digest("hex"); return { timestamp, signature }; }

為什麼是 HMAC,而非 Bearer?

純 Bearer token 的 API 會在每次請求都把你唯一的 secret 上線傳輸。 任何拿到 TLS 終止點代理 log 的人都能取得你帳號的金鑰。HMAC 簽章 代表 secret 永遠不會旅行 — 只傳遞它派生出的簽章,而該簽章是一次性 的(綁定該確切的請求 + 該確切的分鐘)。

代價是:每次呼叫都要計算簽章。Server SDK 會把這部分隱藏起來; 在我們發佈之前,上面的輔助程式碼每種語言約 15 行。

Timestamp 容差

綁定的容差是 ±5 分鐘(300 秒),由 merchant-service 在驗證簽章 時強制執行。Gateway 本身略寬鬆一些(310 秒)作為縱深防禦,但只要 通過 gateway 卻被內層檢查擋下,仍會回 401 invalid_signature — 請以 300 秒作為契約。兩項影響:

  1. 同步你的伺服器時鐘(使用 NTP)。長時間執行的 cron,若時鐘 漂移,會間歇性失敗。
  2. 不要預先計算並排隊簽章。 若一個請求在 retry queue 中停留 >5 分鐘,它的簽章會過期。

Key scopes

Secret key 帶有一或多個下列 scope 組合:

Scope預期用途
read列出 / 讀取訂單、session、退款
write_order建立結帳 session、訂單
write_refund發起退款、鑄造退款申請 token
webhook_manage建立 / 更新 / 刪除 webhook endpoint

儀表板預設發行「full access」金鑰(全部四個 scope)。你可以從 Developers → API keys → + Add key 鑄造受限 scope 金鑰,並只 勾選該整合所需的 scope。

Scope 強制執行目前僅供諮詢,尚未強制阻擋。 Scope 會記錄於 金鑰上、並在儀表板回顯給你,但 gateway middleware 目前還不會 拒絕超出 scope 的呼叫 — 今天任何有效的 sk_… 金鑰都等同於 full access。逐 endpoint 的 scope gating 排在下一個版本。請暫時不要把 scope 當成安全邊界;先把它視為標籤,並透過輪替 / 撤銷金鑰來限制 存取。

簽章在哪裡被驗證

HMAC 驗證在 gateway 上發生一次。Gateway:

  1. 讀取 X-Client-IDX-TimestampX-Signature
  2. pk_… 查找商家 + secret,執行 timestamp 視窗檢查,重新計算 簽章,以 constant-time 比對。
  3. 成功時剝除驗證標頭,為請求蓋上內部標頭(X-B2B-Auth: 1X-Merchant-IDX-Merchant-Domain)後轉送到下游服務 (payment-service、merchant-service 等)。環境與已解析的 scope 目前不會注入到 header — 需要環境資訊的下游程式碼是從請求 body / 每商家的設定推導,而非從 header。
  4. 失敗時直接回 401 INVALID_SIGNATURE,不會碰到後端。

下游服務不會重跑 HMAC — 它們信任 gateway 注入的標頭,並以 gateway 解析出的商家身份運作。它們也不會逐 endpoint 做 scope-gate: 如上所述,金鑰的 scope 不會被注入,因此任何通過驗證的 sk_… 都能 抵達其商家的任意 endpoint(scope 強制執行目前僅供諮詢 — 見 Key scopes 下的提示框)。這帶來兩個重點:

  • 若你在 InfraIO Pay 前面運作自己的反向代理,請勿剝除 X-B2B-Auth / X-Merchant-ID(也別偽造它們 — gateway 會在公開邊緣拒絕帶有 這些標頭的入站請求)。
  • 公開網路路徑(/b2b/v1/*)是唯一執行 HMAC 步驟的介面。我們服務 之間的內部 gRPC 使用 mTLS — 那是一種不同的信任模型,且不接受 X-Client-ID

下一步

  • 錯誤 — 4xx/5xx 的回應格式。
  • 安全 → API 金鑰 — 輪替、撤銷,以及 secret 外洩時該怎麼做。
  • Webhooks → 簽章驗證 — 使用不同的 HMAC 機制(標頭 X-Signature: sha256=…,簽 X-Timestamp + "." + raw_body,加上 24 小時輪替寬限視窗內的選用 X-Signature-Prev)。請別把兩套機制混用 — 雖然 hash 演算法相同, 但所簽位元組與 secret 系列(whsec_… vs sk_…)都不一樣。