This article was drafted with AI assistance and checked against the sources listed.
Key takeaways
- RevenueCat is not an attribution network: apart from its own Apple Search Ads integration, it sends revenue events to attribution providers rather than attributing installs to ad campaigns.
- A RevenueCat purchase joins an install through the App User ID, which the app must also report to its attribution SDK at launch and after every logIn.
- RevenueCat reports a refund as a CANCELLATION event with a negative price, not as a separate refund event type.
- A RevenueCat webhook endpoint should answer 200 within 60 seconds; otherwise RevenueCat retries up to 5 times over about 80 minutes with the same event id.
- RevenueCat's own Apple Search Ads integration stores campaign, ad group and keyword, and groups other installs as Organic.
RevenueCat attribution means joining each purchase RevenueCat reports to the install that produced it, and through that install to the ad campaign. RevenueCat knows the purchase and the App User ID, the identifier it keeps for each customer. Your attribution tool knows the install and its campaign. The join works when the app reports the same App User ID to both, and RevenueCat's webhook delivers every purchase, renewal and refund to the attribution tool.
This guide covers what the webhook sends, how to authenticate it, how the join works, and what renewals and refunds do to revenue. It ends with a step-by-step RevenueCat connector setup. A connector here means a webhook endpoint plus one SDK call that reports the App User ID.
Why RevenueCat does not know most campaigns
RevenueCat's documentation states its role plainly: "RevenueCat isn't an attribution network." Its attribution integrations send subscription and revenue events from RevenueCat's servers to a provider. The provider then matches those events to its own users and campaigns. The list includes Adjust, AppsFlyer, Branch, Kochava, Singular and Tenjin.
Apple Search Ads is the one exception. After the app calls enableAdServicesAttributionTokenCollection(), RevenueCat fetches attribution data from Apple through AdServices and stores the campaign, ad group and keyword. This needs iOS 14.3 or later, and RevenueCat says to allow up to 7 days for the data.
Transactions not attributed to an Apple Search Ads campaign are grouped as Organic in that integration (RevenueCat also shows Unspecified for Apple Ads Basic accounts). An install that came from a Meta or TikTok ad therefore lands in Organic there. For those networks, the campaign has to come from the tool that saw the click, and RevenueCat's events have to reach that tool.
What a RevenueCat webhook sends
A RevenueCat webhook is an HTTPS POST that RevenueCat sends to a URL you configure each time a customer event happens. Each request carries one event. These types change revenue:
| Event type | RevenueCat's definition | Effect on revenue |
|---|---|---|
| INITIAL_PURCHASE | A new subscription was purchased | First sale, or a trial start at price 0 |
| RENEWAL | A subscription renewed, or a lapsed user resubscribed | A recurring sale |
| NON_RENEWING_PURCHASE | A purchase that won't auto-renew | A one-off sale |
| CANCELLATION | A subscription or purchase was canceled or refunded | A refund when the price is negative |
| REFUND_REVERSED | A refund was reversed | The sale counts again (App Store only) |
| TRANSFER | Purchases moved between App User IDs | None, but it moves the join |
A few fields carry the attribution. app_user_id is the last App User ID seen for the customer, original_app_user_id the first, and aliases every one ever used. app_id is the public identifier of the RevenueCat app the event belongs to.
price is the transaction in US dollars and price_in_purchased_currency is what the customer paid. Both are 0 for free trials, negative for refunds, and null when unknown. environment is SANDBOX or PRODUCTION.
Two timestamps differ. purchased_at_ms is when the transaction happened, while event_timestamp_ms is when RevenueCat generated the event. Date revenue by the purchase time, or a delayed delivery moves a sale to another day.
Authenticating the webhook and handling retries
RevenueCat offers two checks. The first, and the one it recommends, is the Authorization header. You type a secret value into the webhook settings, RevenueCat sends it on every request, and your server refuses any request without it.
The second is HMAC signing, which RevenueCat offers for stronger verification. Each request then carries an X-RevenueCat-Webhook-Signature header shaped t=<timestamp>,v1=<signature>. The signature is an HMAC-SHA256, a keyed hash, of the timestamp, a dot and the raw request body, keyed with the integration's signing secret. RevenueCat suggests refusing a timestamp more than about 5 minutes from your clock, which stops replayed requests.
Your endpoint should answer with status 200, and RevenueCat disconnects after 60 seconds. Any other status counts as a failure. RevenueCat then retries up to 5 times, after about 2.5, 5, 10, 20 and 40 minutes, roughly 80 minutes in all.
Retries reuse the same event id. A consumer that stores each id and ignores a second copy never counts a sale twice, which is what RevenueCat advises.
How a purchase joins an install
The join key is the App User ID. When the RevenueCat SDK is configured without one, it generates a random ID prefixed with $RCAnonymousID: and stores it on the device. When the app later calls logIn with your own ID, RevenueCat can alias the anonymous ID to it. Webhooks then carry your ID as app_user_id and the anonymous one as original_app_user_id.
The attribution tool holds the other half. Its SDK reports the first open, which it ties to a campaign click, so the install already has a campaign. On Android, a click identifier can travel through Google Play in a tagged link's install referrer. For the two records to meet, the app hands the attribution SDK the value of Purchases.shared.appUserID at launch, and again after every logIn.
When a webhook arrives, the tool looks up app_user_id, then original_app_user_id, then each alias. The purchase inherits the campaign of the install it finds. That order covers a purchase made after login by someone who first opened the app anonymously.
Both sides must hold the same App User ID; the webhook's IDs are tried in order.
Apple's SKAdNetwork cannot stand in for this join. Its postbacks go to the ad network, carry no user or device data in their signed part and arrive days later, so they describe a campaign's installs without naming any one of them.
Two details break the join if ignored. A reinstall clears RevenueCat's local cache, so an anonymous user gets a new ID on the new install. RevenueCat also advises against email addresses as App User IDs and recommends a random UUID, because customer status is readable through its public API.
Renewals, refunds and test purchases
Each RENEWAL event names the same customer as the first purchase, so it joins the same install. A campaign's revenue therefore keeps growing after its spend has stopped.
Refunds have no event type of their own. RevenueCat reports a refund as a CANCELLATION with a negative price, and a refund made through Apple support carries the cancel reason CUSTOMER_SUPPORT. The event fires only when the latest subscription period is refunded, not an earlier one.
Book a refund as negative revenue against the sale it reverses, instead of deleting the sale. The history stays auditable, and a REFUND_REVERSED event can restore the amount later.
Test traffic needs filtering. Each webhook can send production events, sandbox events or both, and a TEST event arrives when you issue a test webhook from the dashboard. A sandbox purchase proves the pipeline works, but it must never count as revenue or reach an ad network as a conversion.
Do not filter out cancellations
RevenueCat lets a webhook send only some event types. A filter that drops CANCELLATION also drops every refund, and campaign revenue then stays too high.
Setting up a RevenueCat connector, step by step
The steps below use the AttriKit RevenueCat connector, documented in full in the RevenueCat integration guide. They take one sitting if the iOS SDK already reports first opens.
- Copy the webhook endpoint. On the Integrations page of the dashboard, the RevenueCat card shows the shared webhook endpoint,
https://attrikit.io/v1/webhooks/revenuecat. If the card shows none, write to contact@clashware.com before you continue. - Add the webhook in RevenueCat. In your RevenueCat project, open Integrations, then Webhooks, and add a configuration. Paste the URL, set a long and unique Authorization value, and send both production and sandbox events so a sandbox purchase can test the setup.
- Enter the public app ID. Copy the public app ID from RevenueCat's project settings, not the
appl_SDK API key. Enter it on the RevenueCat card with the same Authorization value. The connector finds your workspace from each event'sapp_idand refuses requests that carry neither that value nor a valid signature. - Turn on signing. Enable HMAC webhook signing on the same RevenueCat webhook, then save the secret it shows as
signing_secretthrough the Management API. A delivery is accepted on the Authorization value or on a valid signature, and a signature more than five minutes old is refused. - Report the App User ID from the app. Call the identify method at launch and after every successful
logIn, as in the code below. - Watch the first event arrive. Make a sandbox purchase. Within seconds it appears on the Live events page with the source revenuecat.
- Make RevenueCat the revenue sender. In the Event senders card, assign Purchases, Subscriptions and Refunds to RevenueCat (and Trials, if the app reports trial starts only through RevenueCat), so ad networks receive each revenue event exactly once. If the app reports purchases only through RevenueCat and another sender is chosen, those events never reach the network.
Purchases.configure(withAPIKey: "appl_your_key")
AttriKit.start(apiKey: "pk_your_key", consent: .measurementGranted)
AttriKit.setUserID(Purchases.shared.appUserID)
// After Purchases.shared.logIn(...) succeeds:
AttriKit.setUserID(Purchases.shared.appUserID)
The iOS SDK guide covers the start call and consent.
Limits and edge cases
A purchase can arrive before the app has reported any App User ID. The connector holds it as deferred and reconciles it once the app reports one of the customer's IDs, so it is neither dropped nor counted twice.
The webhook has no dedicated ad network or campaign field. Its optional subscriber_attributes map carries attribution values only if your app wrote them to RevenueCat, so the join uses the install's own attribution, organic included.
Four kinds of event are stored and shown in Live events but never count as revenue or go to a network. They are sandbox purchases, RevenueCat Test Store purchases, entitlements granted from the RevenueCat dashboard, and Family Sharing access to another member's purchase.
When RevenueCat transfers purchases to another App User ID, that ID inherits the install the original ID was joined to, unless it is already joined to one. A refund is booked as negative revenue against its sale, and a reversed refund counts again without being sent to an ad network.
A custom App User ID containing @, or longer than 256 bytes, is refused and creates no join. RevenueCat's guidance is stricter, at 100 characters, so following it keeps an ID inside both.
Each sale is kept in the currency the customer paid and in RevenueCat's US dollar figure. An app selling in several storefront currencies sees its revenue added up in US dollars, when RevenueCat supplied that figure for every sale in the period.
Before you trust campaign revenue
- The webhook carries your Authorization value, or a valid signature, and anything else is refused.
- RevenueCat sends both production and sandbox events, and sandbox purchases never count as revenue.
- No filter drops CANCELLATION, so refunds reach the campaign.
- The app reports the App User ID at launch and again after every
logIn. - One sandbox purchase lands on an install you can name.
Which setup you need
If Apple Search Ads is your only paid channel, RevenueCat's own integration gives campaign, ad group and keyword for installs Apple attributes. Apple Ads Basic accounts return Unspecified for those fields, and age or gender targeting can return no attribution. Once you buy installs on Meta, TikTok or any other network, you need an attribution tool that recorded the click and receives the RevenueCat webhook. Check the join first: make one sandbox purchase and confirm it lands on an install you can name.
Try it on your own app.
Your first 10,000 qualified installs are free, one time, with no card required.
Questions
What value goes in the RevenueCat webhook Authorization header?
Any long secret you choose. You type it into the webhook settings in the RevenueCat dashboard, RevenueCat sends it unchanged on every request, and the receiving server refuses any request that does not carry it.
Which RevenueCat webhook events matter for revenue attribution?
INITIAL_PURCHASE, RENEWAL and NON_RENEWING_PURCHASE carry sales, CANCELLATION carries refunds when its price is negative, and REFUND_REVERSED restores an App Store refund. TRANSFER matters too, because it moves purchases from one App User ID to another.
Does RevenueCat do Apple Search Ads attribution by itself?
Yes, for Apple Search Ads only. After the SDK collects Apple's AdServices token, RevenueCat fetches the campaign, ad group and keyword from Apple. Other installs are grouped as Organic in that integration.
Is a RevenueCat webhook signature required?
No. The Authorization header is RevenueCat's recommended check, and HMAC signing is an optional, stronger one. With signing on, each request carries a timestamp and an HMAC-SHA256 of that timestamp and the raw body.
Sources
- Webhooks, RevenueCat Documentation · read Oct 28, 2026
- Webhook event types and fields, RevenueCat Documentation · read Oct 28, 2026
- Identifying customers, RevenueCat Documentation · read Oct 28, 2026
- Attribution integrations, RevenueCat Documentation · read Oct 28, 2026
- Apple Search Ads, RevenueCat Documentation · read Oct 28, 2026
Originally published on the AttriKit blog. AttriKit is web-to-app attribution for iOS and Android, built by Clashware Sàrl in Lausanne.

Top comments (0)