Skip to main content
GUIDE · PADDLE8 min read

Paddle (Merchant-of-Record) + TrackLayer: tax-compliant subscription tracking

A practical implementation guide for teams using Paddle Billing as the commercial source of truth while TrackLayer turns lifecycle events into a stable canonical stream for attribution, ad optimization, and warehouse reporting.

Context

Why Paddle matters for global SaaS

Paddle is useful in a tracking architecture for the same reason it is useful commercially: it sits at the legally meaningful point of sale. Because Paddle acts as merchant of record, it handles tax calculation, collection, invoicing, and payment processing across jurisdictions that would otherwise turn global SaaS into a compliance project. That means the billing payload already knows whether a customer paid, in what currency they paid, how much of the total was tax, whether a subscription is trialing or active, and whether a later adjustment reversed part of the revenue.

For TrackLayer, that is the difference between inference and truth. Browser tracking can tell you a checkout probably happened. Paddle can tell you whether the commercial event actually settled, what the financially recognized amount was, and whether that value later changed. For global SaaS teams sending conversion signals to Meta, Google, or warehouse models, that makes Paddle one of the best sources for post-checkout attribution because it combines subscription state, tax detail, and international currency context in one durable system of record.

Readiness

Prerequisites

01

A Paddle Billing account with access to developer settings, notification destinations, and the subscription workflows you want to track.

02

A TrackLayer workspace with API credentials and an inbound webhook endpoint ready for production event ingestion.

03

Durable identifiers carried across checkout and product access, such as customer email, customer ID, subscription ID, transaction ID, and your own account or workspace ID.

04

A written event policy for what counts as `trial_started`, `subscription_started`, `subscription_renewed`, `payment_failed`, `refund`, and `subscription_cancelled` inside your business.

Build

Setup

Step 1

Create a Paddle notification destination

In Paddle Billing, add a webhook destination that points to your TrackLayer inbound endpoint. Subscribe only to the billing and lifecycle events you intend to operationalize. A narrow event set is easier to test, reason about, and deduplicate than a full firehose of every entity mutation.

Paddle → Developer tools → Notifications → New destination

Destination URL
https://edge.tracklayer.io/v1/webhooks/paddle

Recommended initial events
subscription.created
subscription.activated
subscription.updated
subscription.paused
subscription.canceled
transaction.paid
transaction.updated
adjustment.updated
Step 2

Store the signing secret in TrackLayer

Copy the Paddle endpoint secret into TrackLayer so inbound signatures can be validated before any event is normalized or forwarded. This protects the attribution layer from forged requests and keeps retries safe because TrackLayer can trust the source event ID when deduplicating deliveries.

TrackLayer → Sources → Paddle

TRACKLAYER_PADDLE_ENDPOINT_SECRET=pdl_ntfset_xxxxxxxxx
TRACKLAYER_API_KEY=tl_live_xxxxxxxxx

Verification status
source: paddle
signature: valid
mode: live
Step 3

Map Paddle events into canonical names

Treat Paddle webhook names as transport detail, not as your company-wide reporting language. Build a small mapping layer so `subscription.activated` becomes `subscription_started`, a renewal-side `transaction.paid` becomes `subscription_renewed`, and a failed collection surfaced through `transaction.updated` with `past_due` status becomes `payment_failed`. This is where TrackLayer earns its keep.

subscription.created     → trial_started or subscription_created
subscription.activated   → subscription_started
transaction.paid         → subscription_started or subscription_renewed
subscription.updated     → subscription_updated
transaction.updated      → payment_failed when status = past_due
subscription.paused      → subscription_paused
subscription.canceled    → subscription_cancelled
adjustment.updated       → refund when approved
Step 4

Add field-level value logic

Use Paddle transaction totals as the financial source of truth, but be explicit about which fields TrackLayer should promote to optimization value. Paddle transactions include tax, discounts, currency conversion, and line-item detail. Decide whether TrackLayer emits gross billed revenue, net software revenue, or both, then keep that choice stable per event type.

Suggested normalized fields
customer_id
subscription_id
transaction_id
event_name
event_timestamp
currency_code
gross_value
net_value
tax_amount
billing_period_start
billing_period_end
source = paddle
Step 5

Simulate the lifecycle before going live

Use Paddle's test webhooks and scenario tools to walk through creation, activation, failed payment, cancellation, and refund flows. Verify not just receipt but meaning: the event should land once, map correctly, preserve currency, and carry the intended value field into downstream destinations. Test renewals and refunds especially, because that is where subscription attribution usually breaks.

Expected TrackLayer output

{
  "source": "paddle",
  "event": "subscription_renewed",
  "paddle_event": "transaction.paid",
  "subscription_id": "sub_01...",
  "transaction_id": "txn_01...",
  "gross_value": 129,
  "net_value": 107.5,
  "currency": "EUR",
  "status": "forwarded"
}
Schema

Event mapping

The important design choice is to keep Paddle-specific webhook names at the ingestion edge and preserve canonical TrackLayer names everywhere else. That way billing can evolve without forcing your ad destinations, internal dashboards, and RevOps workflows to relearn the vocabulary every time Paddle changes a lifecycle detail.

Paddle eventTrackLayer canonicalMapping intent
subscription.createdtrial_started or subscription_createdCreate a trial event when the subscription status is `trialing`; otherwise store the creation record without optimizing on it yet.
subscription.activatedsubscription_startedBest signal for a newly paying subscription after checkout or trial activation.
transaction.paidsubscription_started or subscription_renewedUse billing period and prior subscription state to distinguish first paid billing from renewals.
subscription.updatedsubscription_updatedCapture plan, quantity, scheduled change, or billing-date mutations without inventing revenue.
transaction.updatedpayment_failedMap only when the transaction status moves to `past_due` or another payment-recovery state you treat as dunning risk.
subscription.pausedsubscription_pausedPause is operationally different from cancellation and should stay distinct in audiences and reporting.
subscription.canceledsubscription_cancelledUse the effective cancellation moment, not merely the scheduled change creation, as the final churn event.
adjustment.updatedrefundCreate a refund or negative-value correction when the adjustment is approved.
Optimization

Tax-inclusive revenue

The merchant-of-record benefit creates a measurement decision you should make deliberately: when Paddle bills 129 EUR and part of that total is VAT or sales tax, what value should the media platform learn from? In most SaaS cases, the safer optimization signal is net software revenue, not gross tax-inclusive revenue, because tax is not economic value created by the campaign. If you pass tax-inclusive totals into Meta, Google, or another bidder, you may teach the model that jurisdictions with higher indirect tax rates are intrinsically more valuable customers, which is not usually what finance or growth actually means.

The pragmatic pattern is to keep both fields. Let TrackLayer store `gross_value`, `tax_amount`, and `net_value`, then choose one stable primary `value` field per destination. For Meta purchase optimization, many teams send net value and keep gross for reporting only. If your internal revenue model truly evaluates on gross billed amounts, you can pass gross instead, but make that a conscious policy and use it consistently across first purchase, renewal, and refund adjustments.

Currencies

Customer currency

Paddle supports multi-currency billing, which is useful for conversion rates but dangerous if your downstream systems silently collapse everything into one home-currency number. TrackLayer should preserve the customer-facing transaction currency exactly as billed, along with any normalized finance currency your warehouse uses later. Ad platforms generally expect a value plus currency pair. Give them the real billed currency from Paddle so the signal remains faithful to what the customer actually paid.

The mistake to avoid is mixing currencies at the event layer. Do not send some subscriptions in local currency and others after ad hoc FX conversion unless that rule is universal and documented. Keep event delivery in the original customer currency, perform consolidated FX analysis downstream, and use consistent refund handling in that same currency so optimization and reporting stay internally coherent.

FAQ

Common questions

Paddle Classic or Paddle Billing?

Build the new integration around Paddle Billing. Paddle Billing has the modern API, webhook model, and notification tooling you want for canonical lifecycle tracking. If you still run Paddle Classic, treat it as a migration case rather than the target architecture and keep the mapping logic isolated so you can swap sources later.

How should refunds be signaled?

Use Paddle adjustments as the correction layer. When an `adjustment.updated` webhook indicates an approved refund, TrackLayer should emit a `refund` event or a negative-value correction tied to the original customer, subscription, and transaction references. Otherwise your media platforms keep credit for revenue that no longer exists.

What about dunning and failed payments?

Do not treat every payment hiccup as churn. A failed renewal should usually map to `payment_failed`, remain separate from `subscription_cancelled`, and feed suppression or win-back logic while Paddle continues recovery attempts. Only emit cancellation when the subscription is actually canceled or your own access policy considers the account lost.

How do usage-billed or metered plans fit?

Metered plans usually still resolve into transactions, which means TrackLayer can use paid transaction value as the optimization truth. The useful pattern is to forward the revenue when Paddle says the usage-backed charge is paid, not when the product internally increments a usage counter.

Is Lemon Squeezy the same pattern?

Architecturally, yes. It is another merchant-of-record style billing source that should map into the same canonical TrackLayer lifecycle schema. The exact webhook names differ, but the operating model should not. Keep source-specific adapters thin and preserve one destination-neutral event vocabulary.

Next reads

Related implementation guides

We use essential cookies to keep the site secure and functional. Analytics and third-party tags run only with your consent. See our Cookie Policy.

We use essential cookies to keep the site secure and functional. Analytics and third-party tags run only with your consent. See our Cookie Policy.