Revenue attribution
Revenue connects a checkout to the video someone clicked. Approved sales show on Analytics → Revenue, on the dashboard, and on the video. The numbers are the sales that came back on the webhook, not an estimate.
Where to find it
- Report: open Analytics and select Revenue.
- Setup: open Integrations & APIs and select Revenue.
The report shows month-to-date revenue, total revenue, revenue per play, sales, refunds, revenue over time, revenue by traffic source, and the top videos. Setup is not on the report. Use Revenue setup on the report to open the connection screen.
Connect a checkout
On Integrations & APIs → Revenue:
- Confirm the reporting currency. New sales are converted into this currency for the headline total. Portuguese workspaces start on BRL. Every other workspace starts on USD. You can change it. Sales already stored keep the rate from the day they were charged.
- Choose the checkout: AtomicPay, Hotmart, Kiwify, Cartpanda, Stripe, or Custom.
- Choose the parameter the click key is written into:
src,sck,xcode,xcod,sid,subid,vtid, orsub1. The form starts on the parameter that checkout documents. Stripe does not use this list. - For a custom checkout, enter the host of the buy link, such as
pay.example.com. Named checkouts already know their hosts. - Select Connect, read the steps on the card, and copy the webhook URL into that checkout. The URL contains a secret token. Treat it like a password.
The reporting currency is a dropdown: USD, BRL, EUR, GBP, MXN, or JPY. Those are the currencies the headline can convert. Each order still stores the currency it was charged in.
AtomicPay
Subscribe to the cents webhook for an approved purchase, a completed refund, and a completed chargeback. Use sck. AtomicPay sends that value back on the purchase.
Hotmart
In Hotmart, open Tools → Webhook (API and notifications) and register the URL. Select Purchase approved, Purchase refunded, and Chargeback. Use src. Hotmart returns it as purchase.origin.src on the purchase webhook. The order id is purchase.transaction. The amount is purchase.full_price.value in major units. A refund request is not a refund. Wait for PURCHASE_REFUNDED. How to register the webhook.
Kiwify
In Kiwify, open Apps → Webhooks and create a webhook. Select Compra aprovada, Compra reembolsada, and Chargeback. Use src. Kiwify returns it in TrackingParameters. The order id is order_id. Webhook events.
Cartpanda
In Cartpanda, open Admin → Notifications → Webhooks. Send order.paid and order.refunded to the URL. Use src. Cartpanda keeps src and sck on the checkout link, including stores on mycartpanda.com. Checkout parameters. Webhook events.
Stripe
Stripe Payment Links (buy.stripe.com) do not keep src or the other tracking parameters. Stripe only keeps client_reference_id, and it comes back on checkout.session.completed. The player writes that parameter for you.
In the Stripe webhook endpoint, send:
checkout.session.completedfor the sale. This is the event that carries the click key.charge.refundedandrefund.createdfor a full refund. Stripe also sendscharge.refundedfor a partial refund. A partial refund stays in revenue. A full refund drops out of net.charge.dispute.createdfor a dispute.charge.dispute.closedwith statuswonputs that charge back into revenue.
payment_intent.succeeded has the amount and currency in minor units, but it does not include client_reference_id. That event can record the charge, and it cannot attribute it to a video. On the current Stripe API, invoice.paid does not include the PaymentIntent. A sale saved only as an invoice id cannot be matched to a later refund, which arrives with the PaymentIntent id. Amounts are the charge currency on the PaymentIntent or Checkout Session, not the Adaptive Pricing presentment amount the customer saw.
A Checkout Session created with the Stripe API must set client_reference_id itself. Appending it to checkout.stripe.com does not change a session that already exists. A payment that already completed without client_reference_id cannot be attributed later. Place a new payment after the player update. An approved sale with no click key is listed on the setup screen and is left out of revenue.
Custom checkouts
Named checkouts have their own parsers. Custom does not. It is there for a checkout you control, or any platform that is not in the list. It must POST this JSON to the webhook URL:
{
"order_id": "order_123",
"order_amount_cents": 9700,
"currency": "USD",
"conversion_key": "ap1_click_key_from_the_buy_link",
"product_name": "Course",
"category": "initial_sale",
"occurred_at": "2026-09-28T12:00:00Z"
}
order_amount_cents is an integer in minor units. 9700 is 97.00 USD. conversion_key must be the click key the player added to the buy link. category may be refund, chargeback, upsell, or subscription_renewal. Any other payload is rejected, because each platform uses different field names. If your platform cannot map its webhook into these fields, it cannot be connected as generic.
Send a test sale to confirm the webhook. A test sale is included in revenue and can be removed.
Standard and Scale can connect a checkout and see revenue. Free and Starter can see the setup, but the connection stays locked until the plan includes it.
How a sale is attributed
The player adds a click key to buy links on the page and to buttons inside the video, but only when the link host matches a connected checkout. An existing key is left as it is.
The sale is credited to the last click in the 30 days before the charge. An approved sale with no key, an unknown key, or an expired click is not revenue. It stays on the setup list so you can see that the webhook arrived, and it is excluded from the total, the chart, and every video.
A refund or a chargeback updates that order and removes it from net revenue. A later approval does not turn a refunded order back into a sale.
What the numbers are
The headline is one currency: the workspace reporting currency. A sale charged in another currency is converted with the rate from the sale date and is not rewritten when exchange rates move. Revenue per play is that total divided by plays.
This is not multi-touch attribution, ad-pixel revenue, CPA, or ROAS.
Related docs
Frequently asked questions
Where do I connect Hotmart, Kiwify, Cartpanda, Stripe, or AtomicPay?
Open Integrations & APIs and select Revenue. Each checkout card lists the events to subscribe to and links to that platform's documentation. Paste the webhook URL into the checkout. The Analytics Revenue tab is the report, not the connection form.
Why did Stripe ignore my src parameter?
Stripe Payment Links only keep client_reference_id, and only checkout.session.completed returns it. payment_intent.succeeded has the amount and no click key. The sale still appears, marked as not tied to a video. Add checkout.session.completed, then place a new test payment.
Why did a Stripe refund stay approved?
A sale saved from invoice.paid can have only an invoice id. On the current Stripe API that event does not include the PaymentIntent, and charge.refunded arrives with the PaymentIntent. Send checkout.session.completed, charge.refunded, and refund.created, then resend the refund event from the Stripe dashboard. A partial refund stays approved on purpose.
What does a custom webhook have to send?
order_id, order_amount_cents as an integer in minor units, currency, and conversion_key set to the click key from the buy link. Other JSON shapes are rejected. Named checkouts do not use this JSON.
Which click gets the sale?
The last click in the 30 days before the charge. Only links whose host matches a connected checkout receive a click key.
Do refunds stay in the total?
No. A completed refund or chargeback drops out of net revenue and is listed with refunds. A refund request that has not completed is not a refund.
Why is a sale missing from revenue?
Only a sale tied to a video click is revenue. A webhook with no click key is stored and listed on the setup screen, and it is left out of the total.