Paddle acts as merchant of record, which means it — not your application — owns the checkout, the tax, and the subscription state. Your system finds out what happened through webhooks: a transaction completed, a subscription was cancelled, a payment method updated. If those events do not land, your entitlements drift from what Paddle believes the customer has paid for, and the gap surfaces as a support ticket. This guide covers receiving Paddle events reliably — verifying them, acknowledging fast, and recovering the ones that fail.
What Paddle webhooks are
A Paddle webhook (Paddle calls the destination a notification destination) is an HTTP POST carrying an event about a billing entity. Each event has a type such as transaction.completed or subscription.canceled, a unique event id, an occurred_at timestamp, and a data object holding the entity — a transaction, a
subscription, a customer. Because Paddle is authoritative for billing state and your
app is downstream, these events are how your app learns the truth about what a
customer is owed.
Common use cases
- Provisioning and entitlements — grant or extend access on
transaction.completedand subscription activation events. - Cancellation and downgrades — revoke access or schedule it on
subscription.canceledandsubscription.updated. - Dunning — react to failed-payment and past-due events to prompt the customer.
- Reconciliation — record transactions and adjustments against your own ledger for finance.
Configuring the webhook in Paddle
You configure a notification destination in the Paddle dashboard: a URL, the set of event types it should receive, and — on creation — a secret key Paddle uses to sign deliveries to that destination. Paddle has separate sandbox and production environments with separate destinations and secrets, so a sandbox secret will not verify a production event. Subscribe each destination only to the events it needs.
Exact dashboard labels and the available event types change, so confirm current specifics against Paddle’s webhook documentation rather than memorising a layout. Note that Paddle Billing and the older Paddle Classic differ in their signing schemes; the scheme below is Paddle Billing’s.
Recommended endpoint setup
Paddle expects a prompt 2xx and retries otherwise, so acknowledge first and process
later:
- Capture the raw request body for signature verification before parsing.
- Verify the
Paddle-Signatureheader. - Persist or enqueue the raw event, keyed by its event id.
- Return
2xximmediately. - Apply entitlements, dunning, or ledger writes asynchronously.
Filtering by event type at the edge keeps unrelated events out of your handler. A HookWatch endpoint’s event-type allowlist can drop types a destination does not act on before they reach your service.
Verifying authenticity
Paddle Billing signs each delivery with the Paddle-Signature header, which carries
a timestamp and a hash under a ts/h1 scheme:
Paddle-Signature: ts=1753228800;h1=eec5c3d1a0f8b6...c94 The h1 value is an HMAC-SHA256 computed with your destination’s secret key over a
payload built from the ts value and the raw request body. To verify, rebuild
that signed payload using the header’s ts and the raw body, recompute the HMAC with
your secret, and compare in constant time against h1. Keep the detail modest here
and follow Paddle’s docs for the exact payload construction and any recommended
timestamp tolerance, since those specifics are where implementations drift.
As with every HMAC scheme, verify the raw bytes — a parsed-and-re-serialised body
breaks the hash. The general mechanics are in how to verify webhook signatures. Note that
Paddle’s ts/h1 scheme signs a timestamped payload, not the body alone — so
verify it in your own handler per Paddle’s docs; a generic HMAC-over-body check
(including HookWatch’s configurable inbound verification) does not reconstruct
that signed payload for you.
Common failure scenarios
- Signature failures from a parsed body — a JSON middleware ahead of the verifier
changes the bytes; the
h1no longer matches. - Wrong-environment secret — verifying a production event with a sandbox secret, or vice versa, right after go-live.
- Classic vs Billing mismatch — applying the older Paddle Classic verification to a Paddle Billing delivery (or the reverse) fails every time.
- Delivered but not processed — Paddle records a
2xx, but a downstream enqueue silently dropped the event, so entitlements never update. This class of failure is invisible in Paddle’s dashboard; see webhook delivered but not processed. - Timeouts under load — synchronous provisioning pushes the response past Paddle’s window and the event is retried.
Testing workflow
Paddle provides ways to send a simulated or test event to a destination from the dashboard, and its sandbox environment lets you drive real flows without real money. Because the exact test tooling moves, check Paddle’s current docs for what is available to your account.
For local development, expose your handler with a tunnel and register a sandbox
destination against it so real Paddle deliveries reach your machine over public
HTTPS. See testing webhooks locally; hookwatch tunnel forwards captured deliveries to a local process so Paddle can reach your
laptop through a HookWatch endpoint.
Retry behaviour
Paddle retries failed deliveries with backoff over a limited window rather than forever, and the exact schedule changes, so rely on the behaviour and confirm current numbers in the docs. For your handler:
- A
2xxis the only success signal; a5xx, a timeout, or a connection error is a failed attempt Paddle will retry. - After the window closes, an unrecovered event is gone unless you can re-request or reconcile it, so persistent failures need attention.
- Retries mean the same event arrives more than once — design for at-least-once.
How this shapes your own handler is covered in webhook retry best practices.
Idempotency
Dedupe on the Paddle event id, which is stable across every retry of the same event.
Record processed ids and make provisioning idempotent — extending one subscription
twice, or granting access twice for one transaction.completed, is precisely what
dedupe prevents. A duplicate you have already handled should still return 2xx so
Paddle stops retrying. Full patterns are in webhook idempotency.
Debugging checklist
- Confirm the destination is active and subscribed to the event type in the right environment (sandbox vs production).
- Check the recorded response for the failed attempt — a status or a timeout.
- Confirm the handler reads the raw body before any parser runs.
- Verify the
Paddle-Signatureh1recomputes with the correct destination secret. - Confirm you are applying the Paddle Billing scheme, not Paddle Classic (or vice versa) to match the destination.
- Ensure the handler returns
2xxinside Paddle’s timeout, with work deferred. - Match application logs to the delivery by event id.
- Confirm duplicate events are deduped and still answered
2xx. - If a
2xxwas returned but nothing happened, trace the asynchronous path — the event was delivered but not processed.
How HookWatch helps
Route Paddle through a HookWatch endpoint and every delivery is captured in full —
the Paddle-Signature header, the body, your response, and each attempt — so a
failed subscription.canceled still shows exactly what Paddle sent and what your
handler returned when you investigate later. Once a fix is deployed, replay the exact
captured event (individually or in bulk) without waiting for Paddle’s retry window,
pause an endpoint during maintenance and resume it, and filter to the event types a
destination actually acts on. Repeated failures group into an incident, so a broken
billing hook pages you once with the payload attached rather than leaving entitlements
quietly out of sync.