Webhook 安全最佳實踐
使用獨立於 API Key 的 Ed25519 v2 簽名驗證 PolyPay Webhook。
Webhook 鑑權:Ed25519 v2
v2 使用 PolyPay 平臺簽名金鑰,不需要選擇某個商戶 API Key。驗籤時必須校驗 X-Webhook-Signature-Version、X-Webhook-Key-Id、X-Webhook-Merchant-Id、X-Webhook-Environment、X-Timestamp、X-Nonce 和 X-Webhook-Signature-V2,並從 /api/v1/pay/public/webhook-jwks 獲取公鑰。簽名原文為 polypay-webhook-v2、kid、timestamp、nonce、merchant ID、environment 與原始請求體,以換行符連線。
上線前檢查清單
- 僅允許 HTTPS 回撥地址
- 校驗時間戳並限制重放視窗
- 驗證 Ed25519 v2 簽名與商戶、環境受眾
- 每個事件實現冪等處理
- 異常回撥寫入審計日誌
金鑰輪換策略
PolyPay 透過 JWKS 釋出當前和輪換保留的 Ed25519 公鑰。按 X-Webhook-Key-Id 選擇公鑰並短時快取 JWKS;未知 kid 時重新整理一次,仍不存在則拒絕請求。
冪等處理
對 `event_id` 或鏈上 `hash` 建立唯一處理記錄。重複事件直接返回 200,避免重複發貨或重複記帳。
金額與幣種欄位
`fiat_amount` 是原始 USD 訂單總額,`amount` 是加密幣數量,`exchange_rate` 表示 1 個加密幣對應的鎖定 USD 價值,`currency` 是 USDT、USDC 等加密支付資產,不是帳單法幣。`order_no` 是商戶訂單號,`event_id` 是冪等鍵;`hash` 是鏈上交易雜湊,但管理員手動標記支付(status=7)時可以為空。
排查接收端拒絕
HTTP 409 表示請求已經到達,但接收端業務狀態或資料校驗拒絕了事件。金額不匹配時應對比本地 USD 總額與 fiat_amount;資產不匹配時應對比預期加密資產與 currency,再對比代幣數量與 amount。修正欄位對映或訂單資料後再重發。接收端應儘量返回具體 field、expected、actual 和 event_id,避免只返回籠統的“金額或幣種不匹配”。
失敗重試
Webhook 預設失敗重試次數為 0,即每個事件只自動投遞一次。你可以在 Dashboard 的 Webhooks 頁面調整失敗自動重試次數;手動重發不受該配置限制。
驗籤範例
<?php
use PolyPay\PolyPay;
use PolyPay\WebhookHandler;
use PolyPay\Exception\SignatureException;
$polypay = new PolyPay(getenv('POLYPAY_API_KEY') ?: '');
try {
$event = $polypay
->webhookV2('MCH_YOUR_ID', 'production')
->handle();
$status = WebhookHandler::resolveStatus($event);
if ($status === 'paid') {
handleOrderPaidIdempotently($event);
}
http_response_code(200);
echo 'OK';
} catch (SignatureException $e) {
http_response_code($e->getHttpStatus());
echo 'Unauthorized';
}限流與安全重試
請求超過目前策略額度時,PolyPay 返回真實 HTTP 429。應用響應使用錯誤碼 40001;CDN/WAF 也可能返回 HTML 或純文字,因此客戶端應先檢查 HTTP 狀態,再按 Content-Type 解析響應體。
HTTP/1.1 429 Too Many Requests
Retry-After: 10
RateLimit-Limit: 60
RateLimit-Remaining: 0
RateLimit-Reset: 10
{"code":40001,"message":"RateLimitExceeded","data":{"retry_after":10}}优先读取 Retry-After(可能是秒数或 HTTP-date);缺失时使用带抖动的指数退避。只自动重试可安全重放或具备幂等保护的请求,并设置最大重试次数。
Dashboard 中的 Webhook 測試與手動重發可能被限流。接收端應儘快完成驗籤、持久化後返回 2xx,再非同步處理業務;返回 429 會被視為本次投遞失敗。