Nomba 網路鉤子 是做什麼的?
網路鉤子是 Nomba 用來通知您的伺服器資金確實移動的方式。網際網路上的任何人都可以向您的網路鉤子 URL 發送 POST 請求,因此處理器必須先證明每個請求都來自 Nomba,然後才能將訂單標記為已付款或進行退款。它還必須應對 Nomba 重試相同事件的情況。
請使用官方 SDK 的驗證器;不要手動實作 HMAC。Nomba 的簽章涵蓋了以冒號 (:) 連接的特定欄位,而且這些 SDK 還能封鎖一個簡單實作可能遺漏的重放攻擊技巧。
| 語言 | 驗證方式 |
|---|---|
Node.js / TypeScript (@nombaoss/nomba-node) | verifyWebhook(rawBody, headers, signingKey) |
Python (nomba) | from nomba.webhooks import verify_signature → verify_signature(raw_body, headers, signing_key) |
PHP (nombaoss/nomba-php) | Nomba\Webhook::verify($rawBody, $headers, $signingKey) |
各語言的確切匯入方式及引數類型詳見 references/sdk-setup.md。每個函式都會回傳解析後的事件,或在驗證失敗時拋出例外(WebhookVerificationError / WebhookVerificationException)。這些函式都不需要 API 憑證,只需要從 Nomba 後台(開發者 → Webhook 設定)取得的網路鉤子 簽章金鑰 (signing key)。請將其讀取自環境變數,例如 NOMBA_WEBHOOK_SIGNING_KEY。它與 API 客戶端密鑰是不同的秘密。
處理器,逐步說明
- 取得原始請求主體。 驗證 Nomba 發送的確切位元組。如果 JSON 主體解析器先執行,然後您重新序列化物件,簽章可能會比對失敗,或者更糟的是,您最終驗證的內容與您實際處理的內容不同。每個框架取得原始主體的方式都不同;請參閱 references/frameworks.md 以了解正確方法,並在框架有 CSRF 保護的狀況下,將該路由排除在外。
- 驗證,然後解析。 使用驗證器回傳的事件。驗證失敗時,回應
401(或400)並停止處理。請勿在資訊層級記錄被拒絕請求的完整主體或標頭。 - 根據
requestId去除重複。 Nomba 會重試傳遞,因此相同的事件可能到達多次。使用唯一約束記錄每個已處理的requestId,並跳過已經看過的。對於重複的請求也應回傳200,否則 Nomba 會持續重試。 - 快速回應。 迅速回傳
200,並將耗時的工作(如發送電子郵件、訂單履行、帳本更新)放入以requestId為鍵值的背景工作中處理。緩慢的處理器會導致超時和重新傳遞。 - 根據下表處理事件。 透過參考編號將事件與您自己的記錄進行匹配,並在履行前檢查金額和幣別是否符合預期。
每個事件的意義
event_type | 比對依據 | 應執行動作 |
|---|---|---|
payment_success | 結帳:data.order.orderReference;虛擬帳戶/轉入:data.transaction.aliasAccountReference 或 transactionId | 在檢查 data.transaction.transactionAmount(及幣別)與您的訂單一致後,標記為已付款。對於令牌化結帳,將 data.tokenizedCardData.tokenKey(如果存在且不等於 "N/A")與客戶一起儲存,僅限伺服器端。 |
payment_failed | 同上 | 將嘗試標記為失敗;讓客戶重試。未收取任何費用。 |
payment_reversal | 同上 | 您收到的款項已被沖正。撤銷履行或標記為待審核。 |
payout_success | data.transaction.merchantTxRef | 標記轉帳或帳單付款已送達。 |
payout_failed | data.transaction.merchantTxRef | 請勿立即退款。 Nomba 可能會透過其他途徑重試。將其標記為失敗並等待。 |
payout_refund | data.transaction.merchantTxRef | 資金已回到您的 Nomba 錢包。現在可以安全地退款或記入客戶帳戶,並將付款標記為失敗。 |
在 payout_failed(或超時時)而不是 payout_refund 時退款,是商家重複付款給客戶的經典錯誤。對於任何無法識別的事件,請記錄下來並回傳 200。
使用 API 確認金錢決策。 Nomba 的簽章僅涵蓋 event_type、requestId、data.merchant.userId、data.merchant.walletId、data.transaction.transactionId、type、time、responseCode 和時間戳。金額、orderReference、merchantTxRef 和卡片 tokenKey 並未簽署。因此,在您履行、退款或儲存卡片令牌之前,請使用 SDK 再次查詢,並根據 API 的回應採取行動:對於結帳付款使用 transactions.get({ orderReference })(生產環境中也使用 checkout.getTransaction),對於付款使用 transactions.get({ merchantTxRef })。根據已簽署的 requestId 進行去重,可以防止您已處理過的事件被修改的副本影響,而再次查詢則涵蓋了其餘部分。
重放攻擊防護
根據 requestId 去重是主要的防禦手段:已處理事件的重放或重試傳遞不會產生任何影響。
驗證器也可以拒絕舊的時間戳(toleranceSeconds / tolerance_seconds / PHP 中的第四個引數),但預設情況下應關閉此功能。Nomba 將 nomba-timestamp 記錄為負載發送的時間,但未說明自動重試是否會獲得一個新的時間戳。如果它們保留原始時間戳,則一個小於 Nomba 重試視窗的容錯時間會拒絕停機後的合法重試,導致已付款的訂單永遠無法被確認。只有在沙箱環境中確認重新傳遞會攜帶新的時間戳後,才啟用此功能,並且要保持寬鬆的設定(例如一小時),同時確保伺服器時鐘已同步。
範例 (Node.js + Express)
import express from "express";
import { verifyWebhook, WebhookEventType } from "@nombaoss/nomba-node";
const app = express();
// 僅為此路由設定原始主體解析器;請在應用程式全域的 express.json() 之前註冊它。
app.post("/webhooks/nomba", express.raw({ type: "*/*" }), async (req, res) => {
let event;
try {
event = verifyWebhook(req.body, req.headers, process.env.NOMBA_WEBHOOK_SIGNING_KEY!);
} catch {
return res.sendStatus(401);
}
// requestId 的唯一約束;如果已處理過則回傳 false。
const isNew = await webhookEvents.recordOnce(event.requestId, event.event_type);
if (isNew) await jobs.enqueue("nomba-webhook", event); // 在背景處理
res.sendStatus(200);
});
Python(Flask/FastAPI/Django)、PHP(Laravel/原生)和其他 Node.js 框架的範例,請參閱 references/frameworks.md。
測試
- 使用 SDK 文件中記錄的測試向量對處理器進行單元測試:主體使用 SDK 中的
webhook-fixture.json,金鑰使用SampleSignatureKey123,標頭使用nomba-timestamp: 2025-09-29T10:51:44Z和nomba-signature: q30yFZDyykoymBf9+mt1wz5UAuLk57raPGPHlGzIv00=。驗證錯誤的金鑰、被篡改的主體以及缺少標頭的情況都會被拒絕,並且重複的requestId只會被處理一次。 - 端到端測試:在後台設定一個測試用的網路鉤子 URL 和簽章金鑰,使用隧道工具暴露您的本地伺服器,並進行沙箱環境的付款。後台可以重新推送或除錯傳遞 (https://developer.nomba.com/docs/api-basics/troubleshoot-webhooks)。
檢查清單
- 原始主體在使用 SDK 驗證之前,不進行任何解析或業務邏輯處理。
- 簽章金鑰來自環境變數,而非寫死在程式碼中。
- 簽章錯誤時回傳 401/400;不執行任何其他操作。
- 使用唯一約束對
requestId進行去重;重複的請求仍回傳 200。 - 耗時工作在背景執行;處理器快速回傳 200。
- 在履行前,根據訂單檢查金額和幣別。
- 僅在收到
payout_refund(或再次查詢 API 得到REFUND狀態)時才進行退款。 - 該路由已排除 CSRF 保護,並使用 HTTPS。