Community程式設計與開發github.com

Nomba 網路鉤子

建立一個安全的 Nomba 網路鉤子端點,能驗證簽章、去除重複傳遞,並正確處理每個事件(payment_success、payment_failed、payment_reversal、payout_success、payout_failed、payout_refund)。

Nomba 網路鉤子 是什麼?

此技能提供一個完整、安全的 Nomba 網路鉤子處理器,適用於 Node.js (Express/Next.js/NestJS/Fastify)、Python (Flask/FastAPI/Django) 及 PHP (Laravel/Symfony/原生) 等框架。其核心功能包括:使用官方 SDK 驗證 `nomba-signature` 簽章以防止偽造請求、透過資料庫唯一約束去除重複的 `requestId`、根據事件類型(如付款成功、失敗、退款)執行對應的業務邏輯,以及強調在執行耗時操作前,應再次向 Nomba API 查詢確認金額與狀態,以避免雙重支付或錯誤退款。此技能也涵蓋重放攻擊防護、時間戳容錯設定、測試方法,並提供完整的實作檢查清單,確保開發者能快速且正確地整合 Nomba 的支付通知。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/NombaOSS/skills/tree/HEAD/skills/nomba-webhooks

Installed? Explore more 程式設計與開發 skills: steipete/bluebubbles, steipete/eightctl, steipete/blucli · View all 6 →

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

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 客戶端密鑰是不同的秘密。

處理器,逐步說明

  1. 取得原始請求主體。 驗證 Nomba 發送的確切位元組。如果 JSON 主體解析器先執行,然後您重新序列化物件,簽章可能會比對失敗,或者更糟的是,您最終驗證的內容與您實際處理的內容不同。每個框架取得原始主體的方式都不同;請參閱 references/frameworks.md 以了解正確方法,並在框架有 CSRF 保護的狀況下,將該路由排除在外。
  2. 驗證,然後解析。 使用驗證器回傳的事件。驗證失敗時,回應 401(或 400)並停止處理。請勿在資訊層級記錄被拒絕請求的完整主體或標頭。
  3. 根據 requestId 去除重複。 Nomba 會重試傳遞,因此相同的事件可能到達多次。使用唯一約束記錄每個已處理的 requestId,並跳過已經看過的。對於重複的請求也應回傳 200,否則 Nomba 會持續重試。
  4. 快速回應。 迅速回傳 200,並將耗時的工作(如發送電子郵件、訂單履行、帳本更新)放入以 requestId 為鍵值的背景工作中處理。緩慢的處理器會導致超時和重新傳遞。
  5. 根據下表處理事件。 透過參考編號將事件與您自己的記錄進行匹配,並在履行前檢查金額和幣別是否符合預期。

每個事件的意義

event_type比對依據應執行動作
payment_success結帳:data.order.orderReference;虛擬帳戶/轉入:data.transaction.aliasAccountReference 或 transactionId在檢查 data.transaction.transactionAmount(及幣別)與您的訂單一致後,標記為已付款。對於令牌化結帳,將 data.tokenizedCardData.tokenKey(如果存在且不等於 "N/A")與客戶一起儲存,僅限伺服器端。
payment_failed同上將嘗試標記為失敗;讓客戶重試。未收取任何費用。
payment_reversal同上您收到的款項已被沖正。撤銷履行或標記為待審核。
payout_successdata.transaction.merchantTxRef標記轉帳或帳單付款已送達。
payout_faileddata.transaction.merchantTxRef請勿立即退款。 Nomba 可能會透過其他途徑重試。將其標記為失敗並等待。
payout_refunddata.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。

相關技能