Documentation
Reporting revenue
Stripe by webhook, everything else by one POST from your server.
Stripe
Add the endpoint below in your Stripe dashboard and paste the signing secret into Setup → Revenue.
https://api.roassensor.com/api/tracking/webhooks/stripe/YOUR_PUBLIC_KEYAttribution runs inside the webhook, so your dashboard is correct the moment Stripe gets its 200 back, and a Stripe retry re-runs attribution too.
Conversions API
For any other processor, or revenue only your server knows about. Authenticated with your secret API key, which must never reach a browser.
curl -X POST https://api.roassensor.com/api/tracking/conversions \
-H "X-Api-Key: $ROAS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"external_id": "order_10184",
"amount": "149.00",
"currency": "USD",
"event_type": "purchase",
"email": "[email protected]",
"vid": "the roas_vid cookie, if you have it",
"product_id": "growth-annual"
}'Multiple server keys
The key above is your app's Default key, shown on Setup → Revenue → Your own server. If you run more than one backend service that reports sales — a checkout server and a separate subscription-renewal job, say — give each its own named key instead of sharing one.
The reason: all your services used to have to share one secret, so a key leaking from any one of them meant rotating it and updating every other service too, at the same moment. A named key can be revoked on its own — the others keep working.
- Open the same Your own server section and look for "Running more than one server? Add a key per service."
- Give it a name ("Checkout server", "Subscription renewals") and it's live immediately — no waiting, nothing to verify, since this is a key we mint, not a credential we check with a third party.
- Each key shows when it was last used, so you can tell whether a service is actually using the one you gave it.
- Revoke, not delete: the key stops working the instant you revoke it, but the row stays so you can still see it existed if a service suddenly starts failing and you need to check why.
Fields
external_id and amount are required; every optional field you add makes the match stronger.
event_type:purchase,subscribe,leadorrefund.vid: theroas_vidcookie. The single strongest signal you can pass.email/phone: matched against the identity graph, hashed on arrival.click_id: the ad click ID, if your checkout carried it through.occurred_at: when the sale actually happened, for backfills.product_id: the join key between atrack()funnel event and the sale.is_test: a sandbox sale. Stored and attributed, then left out of reported revenue.
Refunds
amount is always positive. A refund is a separate call with event_type: "refund" and refund_of_external_id naming the original sale. The sign is applied server-side.
A refund mirrors the original's attribution with negative amounts, so net ROAS stays a plain sum. It works even if the original touchpoint has since been pruned.
Currencies
Each app has a reporting currency (App details). Every sale and every day of ad spend is converted into it once, at the rate of the day it happened, and stored that way, so ROAS is always one column divided by another and last month's figure does not move when today's rate does. The original amount and currency are kept on every sale and shown on the Testing tab's stream beside the booked figure.
- Rates. The European Central Bank's daily reference rate first (the fix auditors accept), and for the currencies the ECB does not publish (NGN, PKR, EGP, VND, ARS and the rest of the app-store long tail) an open daily feed. Refreshed hourly; a day with no rate uses the nearest earlier one.
- No rate at all books the sale 1:1 and flags it, and the dashboard reports that figure as *unconverted revenue* rather than averaging a wrong number into a right one.
- Several apps in several currencies on one report are converted into the first app's currency day by day, the same rule, so a 30-day window in a volatile pair does not drift.
- Refunds mirror the original sale at the rate it was booked at, never at today's.
Ad revenue (AppLovin MAX)
If your app earns money by *showing* ads — AppLovin MAX mediation — connect it on an app's Revenue tab and it shows up as Ad revenue on your dashboards, always kept apart from sales.
This is deliberately not called "AppLovin" alone: AppLovin runs two separate products that both use the word "Report Key" for two different credentials. If you also buy ads through AppLovin (the one on the Ad platforms page), the two keys are not interchangeable — one connects spend coming in to you, the other connects money you earn, and they live in different places in the AppLovin dashboard.
- In the MAX dashboard (not the advertiser one), go to Account → Keys.
- Copy the Report Key shown there.
- On the app's Revenue tab, paste it under Ad revenue and press Connect.
It's live within the hour — ad revenue is pulled on the same hourly schedule as ad spend.