Revenue attribution
Send purchases to Pieowl and see which source, campaign, landing page, country and device each sale came from. How purchases are credited, where to see revenue, and what each metric means.
Revenue attribution shows which sources, campaigns, pages and countries make you money, not just which bring visits. You send each purchase to Pieowl, and Pieowl credits it to the visit where it happened.
This page covers how to send purchases, how they are credited, and where to find revenue in the dashboard. For step-by-step checkout flows with Stripe and other payment providers, see Revenue tracking.
Send purchases
Settings → Site → Revenue in your dashboard has everything in one place: your currency, ready code to copy for your site or your server, and the last payment Pieowl received, so you can see it works.
Call purchase() when your site learns the payment went through. The SDKs send it as an event named revenue.
pieowl.purchase({ id: 'A-1001', amount: 49.99, currency: 'EUR', plan: 'pro' });idis your order number. See Order numbers.amountis in normal units, like49.99, not cents. It must be zero or more.currencyis an ISO 4217 code, likeEUR. Leave it out and Pieowl uses your site currency.- Add any other properties you want to break revenue down by, like
planorproduct.
From your payment provider, no code
Stripe, Paddle, Lemon Squeezy and Polar can send your payments to Pieowl by themselves. In Pieowl, open Settings → Site → Revenue → Your payment provider and pick yours. You get your own webhook address and the steps for that provider:
| Provider | Where to add the webhook | Events | Secret |
|---|---|---|---|
| Stripe | Workbench → Webhooks → Create an event destination, payload Snapshot | checkout.session.completed, checkout.session.async_payment_succeeded, invoice.paid, charge.refunded | Copy the Signing secret (whsec_…) into Pieowl |
| Paddle | Events → Notifications → New destination, type URL | transaction.completed, adjustment.created, adjustment.updated | Copy the destination's Secret key into Pieowl |
| Lemon Squeezy | Settings → Webhooks → + | order_created, order_refunded, subscription_created, subscription_payment_success, subscription_payment_refunded | Paste the secret Pieowl shows into Lemon Squeezy |
| Polar | Settings → Webhooks → Add Endpoint, format Raw | order.paid, refund.created, refund.updated | Copy the endpoint's secret (whsec_…) into Pieowl |
From then on:
- One-off payments, subscription payments and renewals are counted. Each event counts once, even when the provider sends it again.
- Refunds are subtracted from the visit the order was paid in.
- Test-mode payments show that the connection works, but they aren't counted.
- The card shows when the provider last sent an event, and what to fix if something is wrong (for example, a wrong secret).
Who paid. The Pieowl script adds the visitor to checkout links by itself: Stripe payment links and buy buttons, Lemon Squeezy and Polar checkout links, and Paddle.js buttons. If your own code creates the checkout, pass the visitor yourself:
- Stripe:
client_reference_id: pieowl.getDeviceId()(ormetadata.pieowl_device_id) - Paddle:
customData: { pieowl_device_id: pieowl.getDeviceId() } - Lemon Squeezy:
checkout_data.custom.pieowl_device_id - Polar:
metadata.pieowl_device_id
Without it the payment counts in your total, without a source. If you also send purchase() from your site for a Stripe payment, use the PaymentIntent id (pi_…) as id, so the payment counts once.
From the browser
Call purchase() with the Web SDK or the script tag. The purchase is credited to the visit that is going on in that browser.
pieowl.purchase({ id: 'A-1001', amount: 49.99, currency: 'EUR', plan: 'pro' });
// Script tag
window.pieowl('purchase', { id: 'A-1001', amount: 49.99, currency: 'EUR', plan: 'pro' });Payment in a modal or an embedded card form
If people pay without leaving the page, there is no success page. Send the purchase where your code learns that the payment went through. For example, with Stripe's Payment Element in a modal, confirmPayment with redirect: 'if_required' returns the paid PaymentIntent for card payments:
const { paymentIntent } = await stripe.confirmPayment({
elements,
redirect: 'if_required',
});
if (paymentIntent?.status === 'succeeded') {
window.pieowl('purchase', {
id: paymentIntent.id,
amount: paymentIntent.amount / 100, // Stripe sends cents
currency: paymentIntent.currency.toUpperCase(),
});
}Some currencies, like JPY, have no decimals. Stripe sends 1500 for ¥1,500, so don't divide those by 100. If your payment provider gives you the amount with decimals, send that.
The visitor is still on the same visit, so the purchase gets that visit's source, pages and country. Payment methods that redirect to a bank come back to your return_url: send the purchase there, as on a success page.
Payment on another site
If the visitor pays on another site, like a hosted checkout page, the simplest way is your payment provider's webhook: no code or from your server. Pieowl stores nothing in the browser to carry the purchase across the redirect.
To send it from the browser instead, call purchase() on your success page. If the page doesn't know the order, look it up on your server, for example with the checkout session ID your payment provider adds to the success address:
// Success address: /thanks?session_id={CHECKOUT_SESSION_ID}
const sessionId = new URLSearchParams(location.search).get('session_id');
const order = await fetch(`/api/orders/${sessionId}`).then((res) => res.json());
pieowl.purchase({ id: order.id, amount: order.total, currency: order.currency });Pieowl rejects purchases and refunds sent from the browser by default, because anyone can send browser events. Turn on Accept revenue sent from the browser in Settings → Site → Revenue to accept them. Sending from your server is more reliable.
From your server
Send the purchase when your server learns the payment went through, for example in your payment provider's webhook. Use the JavaScript SDK with your client secret, or the HTTP API from any language.
import { Pieowl } from '@pieowl/sdk';
const pieowl = new Pieowl({
clientId: 'YOUR_CLIENT_ID',
clientSecret: 'YOUR_CLIENT_SECRET',
});
// When the payment went through
await pieowl.purchase({
id: order.number,
amount: order.total, // 49.99
currency: order.currency, // 'EUR'
deviceId: order.deviceId, // from pieowl.getDeviceId() in the browser
plan: 'pro',
});curl -X POST 'https://api.pieowl.com/track' \
-H 'Content-Type: application/json' \
-H 'pieowl-client-id: YOUR_CLIENT_ID' \
-H 'pieowl-client-secret: YOUR_CLIENT_SECRET' \
-d '{
"type": "track",
"payload": {
"name": "revenue",
"properties": {
"__amount": 49.99,
"__currency": "EUR",
"__orderId": "A-1001",
"__deviceId": "DEVICE_ID",
"plan": "pro"
}
}
}'A server has no visit of its own, so Pieowl needs to know who paid. Send one of these, or both (the deviceId is tried first):
deviceId, when you can. In the browser, read it withpieowl.getDeviceId()before checkout and keep it with the order, for example in your payment provider's metadata. Send it asdeviceIdwith the SDK, or as the__deviceIdproperty over HTTP.profileId. In the browser, callidentify()with the visitor'sprofileIdbefore they pay, for example when they log in or start checkout. Send the sameprofileIdwith the purchase.
Pieowl credits the purchase to that visitor's current visit. If the visit has already ended (a visit ends after 30 minutes without activity), for example because the payment webhook came hours later, the purchase goes to the visitor's most recent visit that started up to 7 days before the purchase. A purchase without a deviceId or profileId, or with no visit in those 7 days, is still saved as a revenue event, but it isn't credited to a visit, so it doesn't show up in the revenue of sources, pages or countries.
Order numbers
Send your order number as id. Pieowl counts each order once, so the same order sent twice adds nothing the second time. This happens more than you'd think: a visitor reloads the thank-you page, or your payment provider retries a webhook.
Use an ID that is unique on your site, like your order number or your payment provider's payment ID. For subscription renewals, use the ID of each invoice, not of the subscription.
Refunds
Call refund() with the order number when you give money back. Pieowl subtracts the order from your revenue.
// The whole order
pieowl.refund({ id: 'A-1001' });
// Part of it
pieowl.refund({ id: 'A-1001', amount: 10, currency: 'EUR' });
// Script tag
window.pieowl('refund', { id: 'A-1001' });- Without
amount, Pieowl refunds what was paid for that order. The order must have been sent withpurchase(). - Each refund counts once. For several partial refunds of one order, give each a
refundId(refunds without one are told apart by amount). - A refund lowers the revenue of the visit the order was paid in, so its source, pages and country lose it too.
Over HTTP, send the same revenue event with "__refund": true and the __orderId. __amount and __currency are optional.
Currency
Pieowl shows all revenue in your site currency. Set it in Settings → Site → Revenue → Your currency. It's USD until you change it.
- You can send purchases in any of these currencies: USD, EUR, GBP, PLN, CHF, CZK, DKK, HUF, NOK, RON, SEK, ISK, TRY, AUD, BRL, CAD, CNY, HKD, IDR, ILS, INR, JPY, KRW, MXN, MYR, NZD, PHP, SGD, THB and ZAR. Any other code is rejected with an error, so a wrong currency never gets counted.
- A purchase in another currency is converted at the European Central Bank reference rate of the payment's day. On weekends and holidays, Pieowl uses the last published rate.
- Pieowl keeps the original amount and currency of each purchase.
- If you change your site currency, past purchases are recalculated in the background. It takes a few minutes.
The older revenue() call
Before purchase(), the SDKs had pieowl.revenue(4900). It still works. It takes a whole number in the smallest unit of your site currency, like cents. It has no order number, so a resent purchase counts twice. Use purchase() for new code.
How a purchase is credited
Every purchase is credited to the visit where it happened. That visit's details become the purchase's details:
- Source and referrer
- UTM campaign, source and medium
- Entry page (the first page of the visit)
- Country
- Device, browser and OS
Other visits by the same visitor get nothing, even if they came first.
Example
A visitor comes to your site twice:
| Visit | Came from | Entry page | Bought |
|---|---|---|---|
| Monday | Google search | /blog/pricing-tips | Nothing |
| Thursday | /products | $49 |
The $49 is credited to Instagram and to the entry page /products. Google and /blog/pricing-tips get no revenue. Both visits still count as visits for their own source and page.
If the visitor had come back on Thursday by typing your address, the $49 would go to Direct.
Where to see revenue
In the overview, switch the metric to Revenue in any of these places:
- Sources: referrers, UTM sources, mediums and campaigns
- Entry pages: revenue by the page each visit started on. Each purchase belongs to one entry page, so the shares add up to 100%.
- Viewed pages: revenue of the visits that viewed each page. A visit that viewed three pages counts for all three, so the shares overlap and add up to more than 100%. Use it to see which pages are part of buying visits.
- Countries and the map
- Devices, browsers and OS
- Events: the revenue of visits that had each event. For example, revenue of visits with
signup_clicked.
Revenue also shows up in:
- People: each person's lifetime revenue, from all of their visits
- Groups and cohorts: revenue for each group and each cohort
Metrics
| Metric | What it means |
|---|---|
| Revenue | The sum of purchases credited to visits in the selected period |
| Paying visits | Visits with at least one purchase |
| Conversion | Paying visits divided by all visits, as a percentage |
| Revenue per visitor | Revenue divided by unique visitors |
Revenue is shown in your site currency.
Filters
Add the Bought filter to the overview:
- Bought: yes shows only visits with a purchase. Use it to see where buyers come from and what they view.
- Bought: no shows only visits without a purchase.
All other filters work with revenue too. Filter by a UTM campaign and every revenue number on the page is for that campaign.
Reports
In reports, revenue works with any event:
- Revenue (visits with this event): pick it as the metric for any event to get the revenue of the visits that had that event.
- Breakdowns by visit: break revenue down by entry page, referrer or UTM fields.
- Funnels and conversion: see the revenue of the visits that completed.
- Formulas: combine revenue with other series.
Example: revenue per visitor over time
- Series A: pick
screen_viewwith the metric Revenue (visits with this event). - Series B: pick
screen_viewwith the metric Unique users. - Click Add formula and enter
A / B.
The formula line is revenue per visitor for each day, week or month.
Privacy
You don't need personal data for revenue attribution. Send the order number and the amount and, from a server, a deviceId or profileId. The profileId can be your internal user ID, it doesn't need to be an email address. Don't put names, emails or card details in the purchase properties.
Pieowl sets no cookies, stores nothing on the visitor's device and doesn't store IP addresses. See How it works.
Revenue by source is conversion measurement. In some countries that needs your visitors' consent, and France's exemption for audience measurement doesn't cover it. Sending purchases from your server or with webhooks keeps payment data out of the browser. See Do you need a cookie banner?.
FAQ
Which currency should I send?
The currency the customer paid in. Pieowl converts it to your site currency at the European Central Bank rate of the payment's day, and keeps the original. See Currency.
Can I import payments from Stripe?
Yes, from the moment you connect it: see From your payment provider, no code. Stripe, Paddle, Lemon Squeezy and Polar then send every payment, renewal and refund by themselves. Payments made before the connection aren't imported.
How do I handle refunds?
Call refund() with the order number. Pass amount for a partial refund. See Refunds.
Can I track subscription renewals?
Yes, send a purchase for each renewal, with the invoice ID as id. A renewal goes to the customer's most recent visit if it started up to 7 days before the renewal. Otherwise there is no visit to credit it to: it's still saved as a revenue event, but it doesn't add to the revenue of sources or pages.
Why doesn't the first visit get any credit?
Pieowl credits each purchase to the visit where it happened. It doesn't split a purchase across earlier visits. Without cookies, Pieowl can't reliably link an anonymous visitor's visits on different days, so crediting the visit where the purchase happened gives numbers you can trust.
Why is my server-side purchase not credited to a source?
Check that you send the visitor's deviceId (from pieowl.getDeviceId()) or a profileId that the browser also passed to identify(), and that the visitor had a visit that started up to 7 days before the purchase.