Paddle webhook in Next js 14 App directory

niraj acharya niraj
Jan 01, 2024 3 min read
Paddle webhook in next js

In previous blog we learned to integrate paddle in next js app. In this blog we will see how we can use paddle webhook in next js app.

Setting up webhook in paddle dashboard

Login to your paddle dashboard and go to developer tools > Notification. Click on New Destination button. Give a description to the webhook and set the notification type to Webhook. Now, you need to enter the url to the webhook. The url doesn’t support localhost so you need to create a tunnel to the localhost. You can use anything like ngrok or localtunnel or Cloudflare tunnel.

I use Cloudflare tunnel personally. You can follow this guide to setup cloudflare tunnel to the webhook endpoint. Once you have setup the tunnel, you can use the url in the form field.

Now, you need to set the events you want to receive in the webhook. You can select the events you want to receive. Once you have selected the events, click on save destination button. Now you are ready to receive the webhook events.

Creating webhook api route

Create a file route.ts in app/api/paddle directory. This file will be used to handle paddle webhook.

import { NextRequest, NextResponse } from "next/server";
import { validateSignature } from "@/utils/paddle";

export async function POST(req: NextRequest) {

  const signature = req.headers.get("Paddle-Signature")!;
  const body = await req.text();
  // mentioned later in the blog
  const isValid = await validateSignature(signature, body, process.env.PADDLE_WEBHOOK_SECRET!);
    if (!isValid)
    return NextResponse.json(
        message: "Invalid webhook signature!",
        status: 401,
    const parsedBody = JSON.parse(body);

    switch (parsedBody.event_type) {
        case "subscription.created":
            // handle subscription created event
        case "subscription.updated":
            // handle subscription updated event
        case "subscription.cancelled":
            // handle subscription cancelled event
        case "transaction.completed":
            // handle transaction succeeded event

     return NextResponse.json(
      message: "done",
      status: 200,

Validating paddle webhook signature

Paddle signs webhook events it sends to your endpoints by including a signature in each event’s Paddle-Signature header. This allows you to verify that the events were sent by Stripe, not by a third party. To verify the signature, you will need a utility function.

async function hashSignature(
  ts: string,
  requestBody: string,
  h1: string,
  secretKey: string
): Promise<boolean> {
  const encoder = new TextEncoder();
  const payload = ts + ":" + requestBody;
  const key = await crypto.subtle.importKey(
    { name: "HMAC", hash: "SHA-256" },
  const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(payload));
  const signatureHex = Array.from(new Uint8Array(signature))
    .map((b) => b.toString(16).padStart(2, "0"))
  return signatureHex === h1;

function extractValues(input: string): { ts: string; h1: string } {
  const matchTs = input.match(/ts=(\d+)/);
  const matchH1 = input.match(/h1=([a-f0-9]+)/);
  return {
    ts: matchTs ? matchTs[1] : "",
    h1: matchH1 ? matchH1[1] : "",

export async function validateSignature(signature: string, body: string, secret: string) {
  const signatureComponents = extractValues(signature);
  return await hashSignature(signatureComponents.ts, body, signatureComponents.h1, secret);

The validateSignature function takes three arguments:

  • signature: The value of the Paddle-Signature header.
  • body: The request body as a string.
  • secret: The webhook signing secret.

It returns true if the signature is valid and false otherwise.


This is how you handle paddle webhook in next js 14 app. You can handle the events as per your need and perform the actions accordingly.

- niraj