Paddle 如何傳遞 Webhook

Paddle 在正式環境大約 2 天內最多嘗試傳遞 60 次,第一個小時大約 10 次。附 ts/h1 簽章格式與 Node 驗證程式。

傳遞行為

取自服務商自己的文件,最後檢查於 2026-08-06。服務商沒寫數字的地方,這張表也不填。

回應時限5 秒

檢查你的處理程式能否及時回應

重試正式環境:大約 2 天內最多嘗試 60 次,第一個小時大約 10 次,第一天大約 45 次。沙箱環境:大約 2 分鐘內嘗試 3 次
放棄服務商未公開
出處https://developer.paddle.com/webhooks/about/respond-to-webhooks

簽章

用一個經測試套件證明可運作的驗證程式核對過,不是照文件抄來的。最後驗證於 2026-08-20。

演算法HMAC-SHA256
簽章內容{timestamp}:{body}
編碼hex
標頭paddle-signature = ts=X;h1=hex
容許時間差5 秒

驗證簽章

要用原始請求主體(raw body),逐位元組原封不動,並在任何 JSON 解析之前取得。框架只要重新序列化 payload,這頁上的每一種機制就會立刻失效。

const crypto = require("node:crypto");

// paddle-signature: "ts=1700000000;h1=abc..." — HMAC-SHA256 over "{ts}:{body}"
const pairs = Object.fromEntries(
  req.headers["paddle-signature"].split(";").map((p) => p.split("=")),
);
const expected = crypto
  .createHmac("sha256", secretKey) // per destination: pdl_ntfset_...
  .update(`${pairs.ts}:${rawBody}`)
  .digest("hex");
const received = Buffer.from(pairs.h1 ?? "");
const wanted = Buffer.from(expected);
// timingSafeEqual throws on a length mismatch, so check that first.
const valid =
  received.length === wanted.length && crypto.timingSafeEqual(received, wanted);

Paddle 的 SDK 強制 5 秒的時間戳記容許時間差,是這裡所有發送端中最嚴的。放在中繼服務或佇列後面時,要拿事件抵達邊緣節點的時間來比對時間戳記,而不是 worker 處理到它的時間。

容易踩的坑

Stripe 的重試曲線稀疏的地方,Paddle 的很密集:第一個小時大約 10 次,第一天大約 45 次,大約 2 天內最多 60 次。只是比較慢的處理程式,一開始每隔幾分鐘就會再執行一次,沒有冪等性,每次執行都會重複副作用。

沙箱環境和正式環境完全不一樣(大約 2 分鐘內嘗試 3 次),所以在測試中看起來沒問題的重試處理,只承受過 5% 的壓力。

延伸閱讀

AnyHook 架在接收 Paddle Webhook 的端點前面:大約 1 秒就回應,伺服器當機時依自己的排程重試,並讓每個事件都能重播。 換一個網址,程式碼不用動;要接受重試與重播,改為檢查 AnyHook-Signature,因為 Paddle 自己的簽章在 5 秒後就會過期。

運作方式 →