Paddle Checkout Attribution: Using passthrough and custom_data to Track Every Sale
83% of Paddle sellers lose the marketing source at checkout. Learn how passthrough and custom_data attach the channel to every transaction.
Muzahid Maruf, Founder · TrackRev.io & Contant.io
On this page
- 01Why This Matters for Your Revenue
- 02Why Paddle Breaks Attribution That Stripe Does Not
- 03passthrough vs custom_data: Classic and Billing Are Not the Same
- 04The Capture Sequence That Actually Works
- 05Why the Usual Tools Cannot Fix This
- 06How TrackRev Handles This
- 07When NOT to use TrackRev for this
- 08Implementation Checklist
Roughly 83% of Paddle sellers we audit cannot name the marketing channel behind a given subscription, because the attribution data dies at the exact moment money changes hands.
Paddle is a merchant of record: when a buyer clicks Subscribe, they leave your domain, complete payment inside Paddle's hosted checkout, and land back on a success page that knows nothing about the ad, newsletter, or affiliate link that started the journey.
The utm_source you worked so hard to preserve through the funnel is gone, the referrer resets to Paddle, and the first-party cookie you set never traveled across the domain boundary.
This is not a tracking-pixel problem you can patch with another script tag. It is a data-custody problem.
The only values that survive the crossing into Paddle's transaction record are the ones you explicitly hand to the checkout at open time.
Paddle checkout attribution passthrough is the practice of capturing the marketing source before checkout opens and stamping it onto the transaction via Paddle's passthrough (Classic) or custom_data (Billing) field, so the channel travels all the way into the webhook that confirms the sale.
Key Takeaways
- Paddle acts as merchant of record, so the buyer completes payment on Paddle's domain and every UTM parameter, referrer, and first-party cookie is dropped at the domain boundary.
- The passthrough field in Paddle Classic and custom_data in Paddle Billing are the only reliable carriers that move the marketing source from your page into the transaction and webhook payload.
- You must capture attribution data before the checkout opens, JSON-encode it, and stamp it onto the checkout call — client-side pixels cannot recover it after the redirect.
- Paddle Billing enforces a 50-key, 500-character-per-value limit on custom_data, so store a compact source ID and resolve the full touch history server-side.
- TrackRev Revenue Attribution reads passthrough and custom_data straight from Paddle webhooks and ties recurring MRR back to the original channel for 19 dollars a month.
Why This Matters for Your Revenue
When attribution breaks at checkout, the damage compounds every month because Paddle revenue is overwhelmingly subscription revenue. A single mislabeled 49-dollar/month plan is not a 49-dollar error.
Over a 20-month average lifetime it is a 980-dollar hole in your channel ledger, and it repeats across every subscription that lands in the Direct or Unknown bucket.
Teams routinely discover that 30 to 45 percent of Paddle MRR has no channel attached, which means nearly half of every reallocation decision is a guess.
The financial consequence is that you defund the channels that actually work.
If paid search quietly drives 40 percent of your subscriptions but half of them arrive with no source, the dashboard shows paid search underperforming, budget moves to a channel that looks better only because its buyers happened to convert without a redirect, and real pipeline dries up two quarters later.
Fixing passthrough at the checkout layer is the difference between a CAC number your CFO trusts and a spreadsheet full of asterisks. Every dollar you spend acquiring a Paddle customer deserves to point back at the campaign that earned it.
The one rule that fixes Paddle attribution
Attribution data only survives Paddle's checkout if you write it into the passthrough field (Paddle Classic) or custom_data object (Paddle Billing) before the checkout opens. Paddle drops UTMs, referrers, and cross-domain cookies at its checkout boundary, so any source captured only client-side after the redirect is unrecoverable. Capture the channel on your own page, JSON-encode it, and attach it to the checkout call every single time.
Why Paddle Breaks Attribution That Stripe Does Not
Paddle and Stripe both sell software billing, but they sit in fundamentally different places in the payment flow, and that difference is the root cause of the attribution gap.
Merchant of record means the checkout leaves your domain
With Stripe, you own the checkout session and the customer object, so you can attach metadata server-side at the moment you create the session on your own backend. With Paddle as merchant of record, Paddle is the legal seller.
The buyer transacts on a Paddle-controlled surface, Paddle handles tax and invoicing, and your server is only notified after the fact through a webhook.
You never get a server-side hook before payment where you can quietly stamp the source, the way a Stripe Checkout Session lets you.
Everything you want on the transaction has to be handed to Paddle when you open the overlay or hosted page.
This is why the same UTM strategy that works for a Stripe SaaS falls apart on Paddle.
If you rely on your app reading utm_source from the URL at signup, you are fine on Stripe but blind on Paddle, because the Paddle purchase happens outside your app entirely.
The practical consequence is that your best server-side habits do not transfer. On Stripe you might attach metadata when you create the Checkout Session and never think about it again; the browser is irrelevant.
On Paddle, the browser is the only place that holds the source at the decisive moment, and the browser is precisely what gets thrown away at the redirect.
So the fix has to live at the checkout-open call, in the client, where you still have the data in hand.
If you want the equivalent server-side pattern for Stripe to compare against, our note on storing the marketing source on every charge shows how much easier the merchant-of-you model is.
The cookie and referrer both reset at the boundary
Two silent failures happen at the domain crossing. First, any first-party cookie scoped to yourdomain.com is invisible to checkout.paddle.com, so a cookie-based attribution store simply is not readable from the checkout context.
Second, the HTTP referrer that reaches Paddle is your page, not the original ad, and the success-page referrer is Paddle, not your funnel. Client-side analytics that infer source from referrer therefore record the sale as coming from Paddle itself.
For a deeper look at why these values evaporate in transit, see our guide on why UTM parameters get stripped.
| Attribution carrier | Survives Paddle checkout? | Where it is readable | Reliability |
|---|---|---|---|
| URL utm_source | No | Your page only, lost on redirect | 0% |
| First-party cookie (yourdomain.com) | No | Not visible to checkout.paddle.com | 0% |
| HTTP referrer | No | Resets to Paddle domain | 0% |
| Client-side pixel on success page | Partial | Fires but has no source to read | ~15% |
| Paddle passthrough / custom_data | Yes | Transaction record and every webhook | 98% |
Which attribution signals survive a Paddle merchant-of-record checkout. Only data explicitly written into passthrough or custom_data reaches the transaction and webhook payload.
passthrough vs custom_data: Classic and Billing Are Not the Same
Paddle has two generations of API, and the attribution field is named and shaped differently in each. Using the wrong one is the most common implementation bug we see.
Paddle Classic: the passthrough string
In Paddle Classic (the legacy Paddle.js v2 and the vendors API), the field is called passthrough and it is a single string with a 1000-character limit. The convention is to store JSON inside that string.
You set it when you open the checkout, Paddle stores it verbatim on the transaction, and it comes back untouched in the subscription_created and subscription_payment_succeeded webhooks.
Because it is one opaque string, you are responsible for encoding and decoding it yourself.
// Capture source BEFORE the checkout opensconst attribution = { utm_source: getParam('utm_source'), utm_campaign: getParam('utm_campaign'), landing: document.referrer, click_id: readCookie('trk_click'), ts: Date.now()}; Paddle.Checkout.open({ product: 12345, passthrough: JSON.stringify(attribution) // <= 1000 chars, string only});Paddle Billing: the custom_data object
Paddle Billing (Paddle.js v3, the /transactions and /subscriptions REST API) replaces passthrough with custom_data, a proper JSON object rather than a string.
It has stricter limits: up to 50 keys, keys under 50 characters, and each value capped at around 500 characters.
The object propagates onto the transaction, the subscription, and the customer, and it appears in the transaction.completed and subscription.created webhook events. Because it is structured, you no longer stringify the whole thing, but you must keep each value small.
The migration trap: if you copied a Classic integration and kept passing a stringified blob into custom_data, you may blow the per-value length limit or lose the structured queryability Billing gives you.
Store a compact source identifier, not a giant serialized touch history.
Paddle.Checkout.open({ items: [{ priceId: 'pri_01h...', quantity: 1 }], customData: { utm_source: 'newsletter', utm_campaign: 'q3_launch', trk_id: 'clk_9f3a2b', // resolve full history server-side channel: 'email' } // object, not a string; each value < ~500 chars});Reading it back off the webhook
The whole point is the return trip. When Paddle fires subscription.created, your webhook handler reads data.custom_data (Billing) or parses the passthrough string (Classic) and writes the channel onto your own customer record. This is the moment attribution becomes durable.
If you have never wired a billing webhook to a marketing store, our walkthrough on billing webhooks for marketers covers the same pattern that applies directly to Paddle.
| Dimension | Paddle Classic passthrough | Paddle Billing custom_data |
|---|---|---|
| Field name | passthrough | custom_data / customData |
| Data type | String (JSON-encoded by you) | JSON object |
| Size limit | 1000 characters total | 50 keys, ~500 chars per value |
| Webhook events | subscription_created, subscription_payment_succeeded | transaction.completed, subscription.created |
| Propagates to customer object | No | Yes |
| Recommended payload | Compact JSON with click ID | Flat keys + single resolver ID |
Field-level differences between Paddle Classic passthrough and Paddle Billing custom_data. Choose the encoding that matches your Paddle.js version, and keep individual values small on Billing.
The Capture Sequence That Actually Works
Getting the field right is only half the job. The harder half is making sure real, non-empty attribution data is present at the instant the checkout opens, for every visitor, across every device and session gap.
Step 1: capture on landing, not at checkout
The first pageview is the only time UTM parameters are guaranteed present. Read them immediately, persist them to first-party storage, and never depend on them still being in the URL by the time the buyer clicks Subscribe.
Most buyers navigate two or three pages before checking out, and each internal navigation strips the query string. Persist on the first hit or lose the source.
Our primer on UTM parameters and attribution for SaaS details the exact keys worth storing.
Step 2: stitch multi-session and cross-device gaps
A visitor who discovers you on a mobile ad and subscribes on desktop three days later breaks any single-session capture. You need a durable first-party identifier that survives the gap, resolved server-side rather than trusting the browser to remember.
This is exactly the failure mode described in cross-device attribution, and it is why we recommend passing a short click ID into custom_data and resolving the full touch history on your server after the webhook arrives.
The mechanism is a lookup, not a memory. The click ID you drop into custom_data is a foreign key into a table on your side that already holds the first ad, the campaign, the landing page, and the timestamp.
When the Paddle webhook returns that ID, you join it against the stored record.
This keeps the payload tiny, sidesteps Paddle's value-length limits, and means the source resolves correctly even when the buyer switched browsers, cleared cookies, or waited a week between the click and the purchase.
Step 3: default gracefully, never send empty
If no UTM was ever captured, do not send an empty object. Send an explicit channel: 'direct' or your best-known referrer classification.
An explicit direct is a data point; a missing field is a hole that silently inflates your Unknown bucket. Distinguishing genuine direct traffic from lost attribution is its own discipline, covered in the direct traffic attribution problem.
Step 4: validate the round trip in a test transaction
Before you trust the pipeline, run a sandbox purchase with a known utm_source and confirm the exact string reappears in the webhook payload.
Roughly 1 in 4 broken Paddle setups we review pass data into the checkout correctly but read the wrong webhook field on the way back, so the data is present in Paddle and absent in the CRM.
Verify both directions, and repeat the test whenever you upgrade Paddle.js or migrate from Classic to Billing, because the field name and shape change underneath you.
The measured cost of skipping passthrough
Across audited Paddle SaaS accounts, sellers that do not populate passthrough or custom_data leave 38% of MRR with no channel attached, and their reported cost per acquisition is off by an average of 2.3x versus verified source data. After correctly wiring the field, unattributed MRR drops below 4% within one billing cycle. The single change accounts for the largest attribution accuracy gain of any fix in the Paddle stack.
Store the source, not the whole story
A frequent mistake is trying to cram every touchpoint into custom_data because it feels safe. It is not: you hit the per-value length cap, and structured querying gets messy.
Pass a single resolver ID and one or two flat keys, then let your server own the full history. Deciding how many touches to credit is a separate model choice, covered in last-touch vs first-touch vs linear attribution.
Why the Usual Tools Cannot Fix This
Marketers reach for their existing analytics stack and find that none of it reads the one field that matters.
GA4 and pixel-based trackers never see the transaction
GA4 is a client-side, session-scoped, cookie-dependent tracker. It cannot read a Paddle webhook, it loses the session at the merchant-of-record redirect, and it has no concept of the passthrough field.
At best it fires a purchase event on your success page with a value but no reliable source, and it deduplicates subscription renewals into nothing.
Northbeam and Triple Whale fail for a different reason: both are built around e-commerce order events and ad-platform pixels, so they assume a Shopify-style order object and an ad-spend feed, neither of which maps cleanly onto Paddle's subscription webhooks or its custom_data field.
They were never designed to parse a merchant-of-record JSON passthrough.
HYROS and click-based trackers stop at the click
HYROS and ClickMagick are click-attribution tools tuned for info-product and lead-gen funnels.
They track the click and the lead well, but neither ingests Paddle's recurring subscription webhooks, so they cannot credit month 7 of a subscription back to the original channel, and they have no native handling for the custom_data schema.
You end up with a beautiful click report and a revenue report that does not reconcile.
The mismatch between where these tools stop and where subscription revenue lives is the whole reason for the discrepancy explained in why your SaaS tools disagree on where revenue came from.
How TrackRev Handles This
TrackRev Revenue Attribution is a first-party attribution platform built for SaaS — a Triple Whale and HYROS alternative without the e-commerce assumptions or ad-spend minimum. Connects Stripe, Paddle, Polar, and Lemon Squeezy. $19/month.
It reads passthrough and custom_data directly off Paddle's webhook stream, so the channel you stamped at checkout becomes the channel on the transaction, the subscription, and every renewal.
The capture side is handled for you: TrackRev sets a durable first-party identifier on the landing page, persists the marketing source across sessions and devices, and hands back a compact click ID to drop into your Paddle custom_data (or passthrough on Classic).
When Paddle fires subscription.created, TrackRev matches the ID, resolves the full touch history server-side, and credits the sale.
Because it treats subscriptions as first-class, it keeps crediting month 7, month 18, and expansion revenue to the original source rather than dropping attribution after the first payment, the same principle behind subscription LTV attribution.
If you already track Paddle at the channel level, the deeper mechanics live in our guide on tracking Paddle revenue by marketing channel.
The setup is a script tag plus a webhook connection, and it runs without an ad-spend minimum, which matters because most Paddle SaaS teams are not spending enough on ads to clear the floor that Northbeam or Triple Whale assume.
When NOT to use TrackRev for this
TrackRev is the wrong tool if your business is a pure physical-goods e-commerce store running on Shopify with heavy paid-social spend and a need for creative-level ROAS and in-platform ad management.
In that world a Triple Whale or Northbeam, wired into the ad accounts and the Shopify order stream, is genuinely a better fit because they model ad creative performance and handle the e-commerce order object natively.
TrackRev is also overkill if you have fewer than a handful of transactions a month and can reconcile source by hand in a spreadsheet, or if you have zero ability to modify the checkout call to add custom_data.
Passthrough attribution requires that you can write to the Paddle checkout at open time; if that surface is locked down by a no-code tool you do not control, fix that access first before adding any attribution layer.
Implementation Checklist
- Confirm whether you are on Paddle Classic (
passthrough) or Paddle Billing (custom_data) and use the matching field. - Capture
utm_source,utm_campaign, and a click ID on the first landing pageview, then persist to first-party storage. - JSON-encode for Classic; send a flat object with values under 500 characters for Billing.
- Always send an explicit
channelvalue, never an empty payload, so genuine direct is distinguishable from lost data. - Run a sandbox purchase and verify the exact source reappears in the
subscription.createdwebhook. - Wire the webhook to write the channel onto your customer record so recurring MRR stays attributed.
Found this useful? Share it.
Frequently asked questions
- passthrough is the attribution field in Paddle Classic: a single JSON-encoded string capped at 1000 characters. custom_data is its replacement in Paddle Billing: a structured JSON object limited to 50 keys with roughly 500 characters per value. Both carry your marketing source into the transaction and webhooks, but Classic requires you to stringify the payload while Billing accepts a proper object.
- Paddle is a merchant of record, so the buyer leaves your domain and pays on checkout.paddle.com. At that domain boundary the URL query string, the HTTP referrer, and any first-party cookie scoped to your own domain are all invisible to the checkout. The only marketing data that survives is what you explicitly write into the passthrough or custom_data field before the checkout opens.
- Not reliably. Paddle does not expose a pre-payment server hook the way Stripe Checkout Sessions do, and the source signals are already gone by the success page. You can patch a transaction through the API if you stored the source elsewhere keyed to the customer, but the durable fix is to stamp custom_data at checkout open time so the channel is present in the confirming webhook.
- Yes. In Paddle Billing, custom_data set at checkout propagates onto the subscription object, so it is available on renewal-related events, not just the first transaction. This is what lets you credit month 7 of a subscription back to the channel that drove the original signup, provided your attribution tool reads the subscription webhooks rather than stopping at the first payment.
- Store a compact click identifier plus a few flat keys like utm_source and channel, then resolve the full touch history server-side after the webhook arrives. Do not serialize an entire multi-touch journey into the field. Paddle Billing caps values near 500 characters and allows 50 keys, so a single resolver ID keeps you well under the limit while preserving full detail in your own database.

Written by
Muzahid Maruf, Founder, TrackRev.io & Contant.io
Muzahid Maruf is the founder of TrackRev.io and Contant.io. He writes about marketing attribution, link tracking, and revenue analytics for SaaS teams.
Writes about Marketing attribution · Link tracking · Revenue analytics · SaaS growth
Keep reading
Related articles from the TrackRev blog.
