Guides / Payments

Get Real-Time Alerts for Paddle Payments

4 min read·Payments

Prerequisites

  • A Paddle account (Paddle Billing)
  • A server endpoint to receive webhooks

Step 1: Register a Webhook in Paddle

In the Paddle Dashboard → Developer Tools → Notifications → New Destination, enter your endpoint URL and enable:

  • transaction.completed
  • transaction.payment_failed
  • subscription.cancelled

Step 2: Handle the Webhook

ts
// app/api/webhooks/paddle/route.ts
import { logit } from "@/lib/logit";

export async function POST(req: Request) {
  const event = await req.json();

  if (event.event_type === "transaction.completed") {
    const txn = event.data;
    const amount = txn.details?.totals?.total ?? 0;
    const currency = txn.currency_code ?? "USD";
    const customerEmail = txn.customer?.email ?? "unknown";

    await logit.now("payments", {
      event: "Paddle payment received",
      description: `${customerEmail}${(amount / 100).toFixed(2)} ${currency}`,
      icon: "💰",
      notify: true,
      tags: { currency: currency.toLowerCase(), status: txn.status },
      metadata: {
        transactionId: txn.id,
        customerId: txn.customer_id,
        customerEmail,
        amount,
        currency,
        subscriptionId: txn.subscription_id ?? null,
      },
    });
  }

  if (event.event_type === "transaction.payment_failed") {
    const txn = event.data;
    await logit.now("payments", {
      event: "Paddle payment failed",
      description: `Customer ${txn.customer_id}${txn.details?.error_code ?? "unknown error"}`,
      icon: "❌",
      notify: false,
      tags: { status: "failed", currency: txn.currency_code?.toLowerCase() ?? "usd" },
      metadata: { transactionId: txn.id, customerId: txn.customer_id },
    });
  }

  if (event.event_type === "subscription.cancelled") {
    const sub = event.data;
    await logit.now("churn", {
      event: "Paddle subscription cancelled",
      description: `Customer ${sub.customer_id} cancelled`,
      icon: "😢",
      notify: true,
      tags: { status: "cancelled" },
      metadata: { subscriptionId: sub.id, customerId: sub.customer_id },
    });
  }

  return new Response("ok");
}

Tips

  • Paddle sends amounts in the smallest currency unit (cents for USD) — divide by 100 before displaying.
  • Add a Smart Action on the payments channel targeting "Paddle payment failed" to alert on failure clusters.
  • Verify the Paddle webhook signature in production using your webhook secret from the Paddle dashboard.

Try LogIt free

7-day trial. No credit card required.

Start free