# How to track lead source in Paddle

Tie every Paddle transaction, subscription, and adjustment back to the marketing channel that drove it. Real revenue attribution for global SaaS, with tax compliance handled.

Source: https://sourceloop.ai/help/track-lead-source-in-paddle/
Updated: 2026-05-28

---

Paddle is the merchant-of-record stack built for global SaaS, billing engine, tax compliance, subscription primitives, all consolidated. The piece it doesn't surface is **which marketing channel actually produced each subscriber**, which is the number you need to know before you decide where to spend.

SourceLoop adds that layer. Every transaction, every subscription state change, every adjustment mapped back to the visitor's first session, so Paddle's "MRR by plan" report finally gets a companion: "MRR by channel".

Five steps, around ten minutes. Works on every Paddle Billing account.

## Why this matters

Paddle's whole value proposition is subscription lifecycle. The math on subscription lifecycle is impossible without attribution that follows the customer through every event:

- A prospect arrives via a LinkedIn ad in February
- They sign up on the Starter plan at $29/mo
- They downgrade to Free in April after a layoff
- They upgrade to Pro at $99/mo in October
- They keep renewing for two more years

Without revenue tracking, LinkedIn gets credited with a "trial started" event and never sees the $2,400+ of lifetime value that came out the other side. With Paddle wired into SourceLoop, every transaction, every plan change, every adjustment hangs off the same lead, and channel-level reporting reflects actual MRR and lifetime value, not just first-touch counts.

## What SourceLoop captures from Paddle

Each Paddle customer arrives in SourceLoop tagged with:

- **Acquisition channel** of the visitor at signup (paid, organic, social, referral, direct)
- **UTM parameters** from the landing URL of the converting session
- **Pages browsed** before the checkout was opened
- **Time on site** and **return-visit count** ahead of the purchase
- **Email + name** from Paddle's customer record
- **First-touch landing page** plus the original referrer
- **Source of the converting session** (often distinct from first-touch)
- **Full subscription lifecycle**, transaction completed (initial + renewals), subscription created / activated (trial converted) / updated / canceled / paused, adjustments (refund, chargeback, credit)
- **Device, country, browser**

## Before you start

You'll need:

- A **SourceLoop workspace** ([free trial](https://app.sourceloop.ai/sign-up))
- **Edit access** to the site that hosts your Paddle.js checkout trigger or backend API
- A **Paddle Billing account** with at least one Price live
- **Admin access** to your Paddle Dashboard, you'll need it to add a notification destination

## Step 1: Install the SourceLoop tracking script

Sign in to SourceLoop, open **Setup -> Tracking code** in the sidebar, and copy the snippet.

![SourceLoop Setup page with the tracking code snippet ready to copy](/help/screenshots/sourceloop-tracking-code-script.png)

Paste it into the `<head>` of every page on your site, especially the pages that open Paddle Checkout. From this point on, every visitor session, UTM, and journey is being recorded. The next steps connect that data to the transactions Paddle will start sending.

## Step 2: Create a notification destination in Paddle

Paddle sends transaction and subscription events through "Notifications" (Paddle's term for webhooks). SourceLoop gives you a dedicated URL, you paste it into Paddle, and Paddle starts delivering events.

1. In SourceLoop, open **Setup -> Payment** in the sidebar.

![SourceLoop Setup Payment page showing the supported payment provider cards](/help/screenshots/sourceloop-payment-page.png)

2. Click the **Paddle** card. A drawer opens on the right with two tabs: **Connect webhook** and **Wire attribution**. Stay on the first tab and copy the **webhook URL** SourceLoop shows you.

![SourceLoop payment provider drawer with the webhook URL and signing secret fields](/help/screenshots/sourceloop-payment-webhook-drawer.webp)

3. Open the [Paddle Dashboard](https://vendors.paddle.com/), navigate to **Developer Tools -> Notifications**, and click **New destination**.
4. Paste SourceLoop's URL into the **Endpoint URL** field.
5. Under **Events**, select the events SourceLoop needs to read. The recommended set: `transaction.completed`, `subscription.created`, `subscription.activated`, `subscription.updated`, `subscription.canceled`, `subscription.paused`, `adjustment.created`.
6. Save the destination. Paddle generates an **endpoint Secret key** (prefixed `pdl_ntfset_...`), copy it.

> **Use sandbox first if you have it**
> Paddle has a separate sandbox environment. Add SourceLoop as a notification destination in your sandbox account first, run a few test transactions, then add it to your live account. Your sandbox connection in SourceLoop is independent of your live connection.

## Step 3: Paste the signing secret back into SourceLoop

Paddle signs every notification with the endpoint secret key.

1. Copy the `pdl_ntfset_...` secret from the destination detail page in Paddle.
2. Back in the same SourceLoop drawer, paste it into the **Webhook secret key** field and click **Save**.

SourceLoop's connection status flips from **pending** to **active** the first time Paddle delivers a verified event through.

## Step 4: Wire attribution into your checkout

The webhook tells SourceLoop **a payment happened**. The attribution wiring tells SourceLoop **which visitor session** that payment belongs to. Without it, events still flow in, but stitching falls back to matching by customer email, lower fidelity, especially when a visitor pays without signing up first.

Once the tracker is installed, it exposes a small helper on every page that returns the two stitching identifiers for the current visitor:

```js
window.sourceloop.checkoutMetadata()
// returns { sourceloop_anonymous_id: '...', sourceloop_id: '...' }
```

Pass that object through to Paddle using the method that matches your checkout. Use this guide to pick:

- **(1) Paddle.js Checkout** — The default for most SaaS. If you open the Paddle checkout from the client by calling `Paddle.Checkout.open()` (overlay or inline), this is you. Covers most marketing-site pricing pages and in-app upgrade flows.
- **(2) Transactions API** — When you create the Paddle Transaction server-side and hand the resulting transaction id to Paddle.js to open the checkout. Recommended for production where you want server-validated prices, pre-applied discounts, or tamper-proof metadata.

Wire each one you use.

### (1) Paddle.js Checkout

Pass the metadata into `customData` when opening the overlay. Paddle propagates it onto every transaction and subscription event that follows.

```js
Paddle.Checkout.open({
  items: [{ priceId: 'pri_xxx', quantity: 1 }],
  customer: { email: userEmail },
  customData: window.sourceloop.checkoutMetadata(),
});
```

### (2) Transactions API

Create the Transaction server-side with `custom_data`, then hand the transaction id off to Paddle.js on the client. This is the recommended production pattern.

Server (Node.js):

```js
const txn = await fetch('https://api.paddle.com/transactions', {
  method: 'POST',
  headers: {
    Authorization:  'Bearer ' + process.env.PADDLE_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    items:       [{ price_id: 'pri_xxx', quantity: 1 }],
    custom_data: meta,
  }),
}).then(r => r.json());

return { txnId: txn.data.id };
```

Client:

```js
Paddle.Checkout.open({ transactionId: txnId });
```

> **Let your AI assistant do the wiring**
> If you'd rather not patch every Paddle integration point by hand, paste the snippets above into Cursor, Claude Code, or your IDE assistant with the prompt "Apply these Sourceloop attribution patterns to every Paddle integration in this codebase. Show me the files you plan to touch before editing." It'll find every `Paddle.Checkout.open` call and every `api.paddle.com/transactions` request, and patch them in one pass.

## Step 5: Run a test transaction to verify

Open the page with your Paddle.Checkout trigger in an **incognito window**, append `?utm_source=test&utm_medium=verify&utm_campaign=paddle-check` to the URL, and complete a real transaction. Paddle sandbox is the safest path if you'd rather not move real money.

Within seconds of Paddle confirming the transaction, the customer should appear at the top of the **Contacts Hub** in SourceLoop with the test UTMs attached and a revenue event linked to the same contact.

> **Not seeing the event?**
> Inside SourceLoop, open the Paddle connection card and check the **Last event** timestamp. If it's stale, the notification isn't reaching SourceLoop, check Developer Tools -> Notifications -> the destination's delivery log in Paddle. If events are received but the contact isn't linking, the most common cause is `customData` / `custom_data` not being passed at checkout, double-check step 4.

## Where to see Paddle revenue in SourceLoop

### Contacts Hub: revenue per contact

Every Paddle customer becomes a contact row at [app.sourceloop.ai/contacts](https://app.sourceloop.ai/contacts) with a **revenue column** showing what they've paid you to date. Click a contact to expand the full revenue ledger, initial transaction, every renewal, every plan change, every refund or chargeback, alongside the visitor's pre-purchase journey.

Filter, sort, or segment the hub by revenue to surface your highest-value customers, or by source to see which channels are bringing in paying users vs. just clicks.

![SourceLoop Contacts Hub showing a Paddle customer with the revenue column, full pre-purchase journey, and revenue history](/help/screenshots/sourceloop-lead-journey-demo.webp)

### Attribution dashboard: revenue by channel

[app.sourceloop.ai/dashboards/traffic](https://app.sourceloop.ai/dashboards/traffic) adds a **revenue column** alongside the breakdowns you already see: source, medium, campaign, landing page, content, term, device, country. So instead of "LinkedIn Ads drove 320 sessions", you see "LinkedIn Ads drove 320 sessions and $6,800 in attributed Paddle revenue."

Switch attribution models from the dropdown at the top to see how the numbers shift:

- **Last Non-Direct** (the default, matches Google Analytics)
- **First Touch** / **First Non-Direct** for top-of-funnel weighting
- **Last Touch** for closing-channel weighting
- **Linear** to split credit evenly across every touchpoint
- **Position-Based (U-Shaped)** for 40% first / 40% last / 20% middle
- **Time Decay** to weight recent touches higher (7-day half-life)

For long sales cycles (LinkedIn Ads → blog → demo → free trial → paid), Last Touch credits the demo while First Touch credits LinkedIn. Time Decay sits in between. Flip between models to see which one tells the truth.

![SourceLoop attribution dashboard with Paddle revenue column grouped by source, medium, and campaign](/help/screenshots/sourceloop-attribution-dashboard.webp)

Once Paddle revenue is flowing, push it back to **Google Ads, Meta, and LinkedIn as offline conversions with revenue values** so the bidding algorithms optimise toward real paying customers, not free-trial signups. [Connect your Google Ads account](/help/connect-google-ads/) covers the wiring.

## Frequently Asked Questions

### Does this work with Paddle.js Checkout and the Transactions API?

Yes. Both surfaces are covered. The webhook setup is identical for both, only the attribution wiring in step 4 differs. The article walks through each method.

### Paddle is a merchant of record. Does that affect the revenue numbers SourceLoop reports?

No. Merchant-of-record handling (VAT, sales tax, payouts) sits entirely on Paddle's side. SourceLoop captures the gross transaction amount and the lifecycle events at face value.

### Are subscription renewals attributed back to the original source?

Yes. Every `transaction.completed` event tied to a recurring billing cycle gets stitched onto the same contact that started the subscription, so MRR stays tied to the channel that earned the original signup.

### How do refunds, chargebacks, and credits show up?

All three flow through Paddle's `adjustment.created` event with `action=refund | chargeback | credit`, and SourceLoop reports them as negative revenue tied back to the original lead. The attribution dashboard nets them against the original source.

### I use Paddle Billing's plan changes (upgrade, downgrade, pause, resume). Are those tracked?

Yes. Every subscription state change generates a `subscription.updated` event with the new status, and SourceLoop attaches it to the existing contact so the timeline reflects the full subscription lifecycle.

### Will Paddle's existing notifications to Slack, my data warehouse, or my own backend still fire?

Yes. SourceLoop subscribes as its own notification destination. Every other destination you've configured continues to receive Paddle events independently.
