You can use webhooks to react to events that happen in your Paddle account, like a new subscription or a renewal. Paddle sends events to a URL you provide, so you can build your own logic to handle them.
This quickstart walks you through forwarding webhooks to a local server, creating a destination, and verifying signatures. It takes about five minutes.
Before you begin
You need a Paddle account. You can sign up for free:
- Sandbox account — for testing. No real money is involved.
- Live account — for production use. Requires approval before you can process real transactions.
We recommend starting in the sandbox environment.
You'll also need Node.js installed locally to run the example handler.
Use an AI agent
Install the Hookdeck CLI
The Hookdeck CLI gives your computer a public URL and forwards incoming webhooks to a port on localhost. It's free for development and the fastest way to receive real webhooks without deploying anything.
Install using your package manager:
pnpm add -g hookdeck-cliyarn global add hookdeck-clinpm install hookdeck-cli -gOr use Homebrew or Scoop:
brew install hookdeck/hookdeck/hookdeckscoop bucket add hookdeck https://github.com/hookdeck/scoop-hookdeck-cli.gitscoop install hookdeckVisit hookdeck.com/docs to learn more about the Hookdeck CLI
Spin up a local listener
Create a minimal webhook handler that accepts POST requests, logs the raw body, and responds with 200 OK. Paddle retries on non-2xx responses, so returning a 2xx quickly is important.
import express from "express";
const app = express();
app.post( "/webhooks", express.raw({ type: "application/json" }), (req, res) => { console.log("Received webhook:", req.body.toString()); res.status(200).send("ok"); });
app.listen(3000, () => console.log("Listening on :3000"));Then, run it:
node server.jsIn a second terminal, start Hookdeck and point it at port 3000:
hookdeck listen 3000 paddle-quickstart --path /webhooksHookdeck prints a public URL like https://hkdk.events/abc123xyz. Copy it for the next step.
Keep both terminals open for the rest of the quickstart. If you stop the Hookdeck CLI, the public URL stops forwarding and webhooks will queue up rather than reach your server.
Create a notification destination
A notification destination tells Paddle which events you want and where to send them.
You can create a notification destination using the API, dashboard, or MCP server. We'll use the dashboard for this quickstart.
- Go to Paddle > Developer tools > Notifications.
- Click New destination .
- Set Notification type to URL and paste the Hookdeck URL from the previous step.
- Choose the events you want to receive — for testing,
customer.createdis a good start. - Click Save destination .
- Open the destination you just created and copy its Secret key. You'll need it for signature verification.
Treat your endpoint secret key like a password. Keep it safe and never share it with apps or people you don't trust.
Send a test webhook
Use webhook simulator to send a test webhook to your destination without having to take any real action, like completing a checkout.
- Go to Paddle > Developer tools > Notifications and click the Simulations tab.
- Click New simulation .
- Choose your notification destination, select the
customer.createdevent, and click Save . - Click Run on your simulation.
Within a second or two, you should see the event in the Hookdeck terminal and the parsed payload logged by your Node server. If nothing arrives, check that Hookdeck is still running and that the destination URL matches the one it printed.
Verify the signature
Every webhook Paddle sends includes a Paddle-Signature header. Verifying it on your side proves the request really came from Paddle — not from someone who found your public URL.
The quickest way to verify is with one of our SDKs. Install the Node.js SDK:
pnpm add @paddle/paddle-node-sdkyarn add @paddle/paddle-node-sdknpm install @paddle/paddle-node-sdkReplace your handler with a version that uses the SDK to verify before acting on the event:
import { Paddle, EventName } from "@paddle/paddle-node-sdk";import express, { Request, Response } from "express";
const paddle = new Paddle("API_KEY");const app = express();
// Create a `POST` endpoint to accept webhooks sent by Paddle.// We need `raw` request body to validate the integrity. Use express raw middleware to ensure express doesn't convert the request body to JSON.app.post( "/webhooks", express.raw({ type: "application/json" }), async (req: Request, res: Response) => { const signature = (req.headers["paddle-signature"] as string) || ""; // req.body should be of type `buffer`, convert to string before passing it to `unmarshal`. // If express returned a JSON, remove any other middleware that might have processed raw request to object const rawRequestBody = req.body.toString(); // Replace `WEBHOOK_SECRET_KEY` with the secret key in notifications from vendor dashboard const secretKey = process.env["WEBHOOK_SECRET_KEY"] || "";
try { if (signature && rawRequestBody) { // The `unmarshal` function will validate the integrity of the webhook and return an entity const eventData = await paddle.webhooks.unmarshal( rawRequestBody, secretKey, signature, ); switch (eventData.eventType) { case EventName.ProductUpdated: console.log(`Product ${eventData.data.id} was updated`); break; case EventName.SubscriptionUpdated: console.log(`Subscription ${eventData.data.id} was updated`); break; default: console.log(eventData.eventType); } } else { console.log("Signature missing in header"); } } catch (e) { // Handle signature mismatch or other runtime errors console.log(e); } // Return a response to acknowledge res.send("Processed webhook event"); },);
app.listen(3000);Set PADDLE_API_KEY and WEBHOOK_SECRET_KEY in your environment variables. Restart your server and run the simulation again. You should now see the verified event type logged.
For all supported languages and manual verification, see Verify webhook signatures.
Next steps
You're set up to receive verified webhooks. These pages cover what to build next:
Learn how Paddle delivers events, the webhook lifecycle, and delivery guarantees.
Best practices for responding to webhooks, including retries and idempotency.
See verification examples for Go, PHP, Python, Ruby, Java, and more.
Manage destinations, rotate secret keys, and subscribe to more events.
Use the simulator to test lifecycle scenarios like subscription renewals and failed payments.
Browse the full event catalog grouped by entity to see every event Paddle sends.