Delivery behaviour
Read from the vendor's own documentation, last checked 2026-08-06. Where the vendor states no number, this table carries none.
| Response budget | 5s |
|---|---|
| Retry | Live: up to 60 attempts over about 2 days, about 10 in the first hour and about 45 in the first day. Sandbox: 3 attempts within about 2 min |
| Gives up | not published by the vendor |
| Source | https://developer.paddle.com/webhooks/about/respond-to-webhooks |
Signature
Verified against a working verifier proven by a test suite, not read from documentation. Last verified 2026-08-20.
| Algorithm | HMAC-SHA256 |
|---|---|
| Signed payload | {timestamp}:{body} |
| Encoding | hex |
| Header | paddle-signature = ts=X;h1=hex |
| Tolerance | 5s |
Verify it
The raw request body, byte for byte, before any JSON parsing. Every scheme on this page breaks the moment a framework re-serializes the 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's SDK enforces a 5-second timestamp tolerance, the tightest of any sender here. Behind a relay or a queue, check the timestamp against when the event arrived at the edge, not when your worker got to it.
What bites
The retry curve is dense where Stripe's is sparse: about 10 attempts in the first hour, about 45 in the first day, up to 60 over about 2 days. A handler that is merely slow runs again every few minutes at first, and without idempotency each run repeats its side effects.
Sandbox behaves nothing like live (3 attempts within about 2 minutes), so retry handling that looks fine in testing has seen 5% of the pressure.
Read more
AnyHook sits in front of endpoints that receive from Paddle: it answers in about a second, retries on its own schedule when your server is down, and keeps every event replayable. Change one URL, keep your code; to accept retries and replays, check AnyHook-Signature, because Paddle's own signature expires after 5s.