Your Paddle dashboard tells you how much you earned. It cannot tell you which campaign earned it. Connecting Paddle joins the two: a payment arrives, and the dashboard already knows where that visitor came from.
What you get
Revenue beside your traffic, in the same date range and the same channels
Which channel brought the money — often not the one bringing the visitors
New and renewal revenue separately, so growth is not hidden inside recurring income
Refunds and chargebacks shown as they happen, and told apart from credits
Twelve subscription goals with no code — trials started and converted, renewals, upgrades, downgrades, pauses, cancellations and churn. Each is usable as a filter. See the subscription goals.
Two goals are Stripe-only, and the reason is Paddle's rather than ours.
Paddle sends no event before a trial ends, so trial_will_end has nothing to
be built from, and it handles 3-D Secure inside its own checkout — reporting
only the outcome, which leaves payment_action_required without a
counterpart. A declined card still arrives as payment_failed.
Connecting
Create an API key
In Paddle, go to Developer tools → Authentication → API keys and create a new key.
Set it to never expire, and tick exactly three permissions:
| Permission | Why we need it |
| Transactions (Read) | The payments themselves — amounts, refunds, product names |
| Subscriptions (Read) | MRR and churn. A cancelled subscription appears in no transaction, so without this the MRR curve has no churn in it |
| Notification settings (Write) | To register one webhook, so payments reach us as they happen |
Paddle ticks Notification settings (Read) for you when you select Write — you cannot grant or withhold it separately, so there is nothing to look for.
An expiring key fails silently. The key form defaults to an expiry date. When a key lapses, webhooks keep arriving and verifying — only the history import and the event-list sync stop, and neither of those announces itself.
Paddle's new-API-key dialog with the three permissions ticked: Transactions (Read), Subscriptions (Read), Notification settings (Write), and the expiry set to never. Light theme, cropped to the permission list.
Paste it back
In TrueStat, open your site's Settings → Integrations, choose Paddle and paste the key. We check every permission before saving; if one is missing, the screen names it.
Site settings, Integrations tab: the Paddle card with the API key field and the "Create a key in Paddle" link, before any connection. Light theme, cropped to the card.
Pass the visitor id at checkout
This is the step that makes attribution work. See below — the code depends on which checkout you use.
Linking a payment to a visitor
Without this, payments still appear — but under unattributed, because nothing connects the payment to the visit that led to it.
Our script stores a visitor id in a cookie. Pass it to Paddle as custom_data,
and Paddle carries it through — including onto every renewal, which is why
renewals stay attributed without any further work.
Which code you write depends on how your checkout opens. Pick the one that matches:
The field is spelled differently in each. custom_data in the REST API,
customData in Paddle.js. Neither rejects the other — the wrong one is
ignored, and the payment arrives unattributed with nothing to say why.
What the numbers mean
The Revenue card with a Paddle connection: total, new versus renewal, and refunds, over the selected range. Light theme, cropped to the card.
Amounts include tax. A $82.50 plan sold into a 20% VAT country is recorded as $99.00, which is what Paddle reports as the transaction total — so the dashboard matches your Paddle dashboard rather than your net-of-tax figure.
Fees are recorded but not subtracted. Each payment carries what Paddle charged and what you earned. The revenue total is the gross, because that is the number Paddle shows you.
A real example, so the three figures make sense together:
| Plan price | $82.50 |
| VAT (20%, this buyer was in Turkey) | $16.50 |
| Total — what the dashboard counts | $99.00 |
| Paddle's fee | −$5.45 |
| Earnings — what reaches you | $77.05 |
The VAT is not yours: Paddle collects it and remits it. So earnings is the plan price minus the fee, not the total minus the fee — which is why $99.00 − $5.45 does not give $77.05.
New versus renewal is decided by where the money came from, not by the amount:
| What happened | Counted as |
| A first payment through checkout | New business |
| A renewal | Renewal |
| An upgrade's prorated difference | Renewal — the customer already existed |
| An add-on charged to a subscription | Renewal — same reason |
| A one-off purchase with no subscription | New business |
An upgrade charges the difference, not the full new price. Moving from $24 to $99 halfway through a month bills about $37.50 — the remaining half of the $75 gap. That is the amount you see, and it counts as renewal revenue because the channel that won that customer had already won them.
A credit is not a refund. Paddle can apply a credit that covers an invoice entirely, leaving the customer paying nothing while the revenue is still real — the money arrived earlier, when the credit was issued. Those payments appear at their full value, and only actual refunds and chargebacks reduce your total.
Unpaid invoices are not revenue. A transaction Paddle has issued but not collected does not appear at all. If it is later paid, it appears then; if it is cancelled, it never does.
Your history comes with you
When you connect, we import what Paddle already has: your completed transactions and your live subscriptions, however far back they go. The panel shows the import running and the numbers fill in as it works.
The Paddle card while the history import is running: the progress line naming how many payments have been imported so far. Light theme, cropped to the card.
What imported payments can and cannot tell you
They carry their attribution. This is a real advantage Paddle has: because
custom_data lives on the transaction itself, a payment imported from a year
ago still knows which visitor bought it, and appears in your channel
breakdowns. Imported Stripe payments cannot do this — Stripe keeps checkout
metadata on an event, and those expire after 30 days.
Plan changes in between are not recovered. We import when a subscription started and, if it happened, when it ended. The upgrades and downgrades in between are events, and Paddle's event stream keeps 90 days — so an upgrade from last year cannot be read by anyone.
Re-syncing later
If the numbers look wrong, or you want to pull history again after fixing something at Paddle, press Re-sync history.
The connected Paddle card showing the last import's summary and the "Re-sync history" link. Light theme, cropped to the card.
Re-syncing never double-counts. A payment already imported is recognised and not written again, so your total does not move however many times you press it. What a re-sync does fix is a payment that is missing information — a fee it did not have, or a classification that has since been corrected.
Reading revenue in another currency
If your Paddle account settles in one currency and you would rather read the dashboard in another, pick it in the panel. Amounts are converted at today's rate for display, and stored in your account currency — so the total moves with the exchange rate, and the underlying figures keep matching Paddle.
Disconnecting
Disconnecting removes the webhook from your Paddle account and deletes the revenue collected so far from this dashboard. Your Paddle data is untouched.
Your traffic is untouched too: pageviews, sessions and your own goals stay exactly as they are. Only the payments, the subscription state and the subscription goals go.
If something is not working
Payments appear as unattributed. The checkout is not passing custom_data,
or it is passing it under the wrong spelling — customData for Paddle.js,
custom_data for the API. Open a recent transaction in Paddle and look for the
field; if it is empty there, it never reached us.
The key was rejected on connecting. The screen names the missing
permission. If it says the environment does not match, the key is a sandbox key
on a live deployment or the reverse — the prefix tells you which:
pdl_live_apikey_… against pdl_sdbx_apikey_….
Nothing arrives after connecting successfully. Check Notifications → Notification destinations in Paddle: ours is called "TrueStat — revenue attribution" and should be active. If it is missing, the key lost its write permission after connecting; reconnecting recreates it.
The MRR figure looks low. A subscription only counts toward MRR while it is active or past due. Trials count as pending rather than as revenue, and a cancelled subscription stops counting the day it actually ends rather than the day the cancellation was scheduled.