Attribution
How RevenueHog links store purchases to your own user IDs with identify and attribute, how anonymous IDs re-point after login, and the Apple Ads token.
Attribution answers two questions the store does not: which of your users made a purchase, and which Apple Ads campaign brought the install. The first uses identify and attribute; the second uses the AdServices token.
Purchase to user attribution
The anonymous-then-login pattern
Purchases often happen before you know who someone is. The SDKs handle this for you; raw HTTP integrations should do the same:
- On first launch, generate a UUID, persist it and use
$anon_<uuid>asappUserId. - Attribute every verified purchase under that anonymous ID as it happens.
- When the user logs in, call
identifywith the real user ID, then re-sendattributefor every transaction you reported anonymously.
Step 3 is the whole aliasing mechanism. One transaction belongs to one user per workspace, so re-posting the same originalTransactionId under a different appUserId moves it and the customer's history re-points. There is no separate alias endpoint.
On logout, drop the stored user ID and generate a fresh anonymous ID (reset() in the SDKs).
identify
POST /api/sdk/v1/identify upserts an app user. Repeat calls merge: attributes shallow-merge into what is stored, device fields update, and the last-seen time bumps. Anonymous $anon_ IDs are ordinary user IDs to the server.
{
"appUserId": "user_42",
"bundleId": "com.example.app",
"platform": "ios",
"osVersion": "26.2",
"deviceModel": "iPhone17,2",
"locale": "en_US",
"attributes": { "plan": "pro" }
}appUserId and bundleId are required. platform accepts exactly ios or android; anything else is ignored rather than rejected. attributes must be a flat JSON object of at most 8 KB. Call it at launch, at login and when attributes change, not on every screen.
attribute
POST /api/sdk/v1/attribute links a store transaction to a user.
{
"appUserId": "user_42",
"bundleId": "com.example.app",
"originalTransactionId": "2000000123456789",
"productId": "com.example.app.pro.monthly",
"jws": "<StoreKit 2 signed transaction>"
}- iOS: send StoreKit's
originalID, the ID Apple keeps stable for the life of a subscription, not a renewal's transaction ID. Include the signed transaction asjwswhenever you have it (VerificationResult.jwsRepresentation, orjwsRepresentationIosin react-native-iap). - Android: send the Play purchase token as
originalTransactionId. When the app is a connected Google Play app, the token is stored as the subscription's lineage key so it joins the Play customer.
Verified, unverified and JWS
| Signal | Effect |
|---|---|
Device token (Bearer dt_…) | The write is attested. The user row is marked attested. |
| No token | The write is stored unverified, if the bundle ID resolves to exactly one app. |
Valid jws | RevenueHog verifies Apple's signature chain. Its bundleId, originalTransactionId and productId override the body, and the link is marked verified even from an unattested caller. Its appAccountToken is stored too. |
Invalid jws | Ignored, as if not sent. Xcode StoreKit testing signs locally, so those never verify. |
Attestation and JWS verification only ratchet upward: a later re-attribution without a JWS (the aliasing flow re-posts raw IDs) keeps the earlier proof.
What you see in the dashboard
On Customers, a customer that came through attribution shows your user ID and reads sdk identify as its attribution source, instead of an anonymous customer_… handle reading organic / app store. Attested traffic carries a VERIFIED badge; quarantined traffic reads UNVERIFIED. See Customers.
Apple Ads attribution token
The SDKs link purchases to users, but they do not capture Apple's AdServices attribution token. To learn which Apple Ads campaign, ad group and keyword produced an install, read the token on the device with AAAttribution.attributionToken() and post it yourself:
POST /api/sdk/v1/adservices
Content-Type: application/json
{
"bundleId": "com.example.app",
"installId": "1B7A2C…",
"token": "<AdServices token>",
"appVersion": "2.4.0",
"storefront": "USA",
"environment": "production"
}Prop
Type
The response is always { "ok": true } once validation passes. Auth works like every SDK endpoint (device token, or the quarantine path).
First touch wins. The (bundleId, installId) pair is recorded once. A second token for the same install is a no-op and never overwrites the campaign that first earned the user.
Resolution happens later. Apple answers 404 until its payload is ready, so RevenueHog does not resolve inline. A background sweep exchanges the token with https://api-adservices.apple.com/api/v1/, retrying with a backoff ladder of 30 s, 1 min, 5 min, 15 min, 1 h, 3 h and 6 h. Apple's token is valid for 24 hours; after that there is nothing to retry.
Test installs. Tokens minted outside the App Store (TestFlight, Xcode, sandbox) return Apple's placeholder payload with campaign ID 1234567890. RevenueHog stores those with status test and excludes them from the numbers. When a user disables Personalized Ads, the keyword is absent but attribution still resolves.
The resolved campaigns feed the Attribution dashboard alongside Apple Ads spend.
Related
Last updated on
React Native SDK
Add the pure JavaScript RevenueHog SDK to a React Native or Expo app, including Expo Go, to attribute purchases, identify users and serve paywall SKUs.
Signpost events
Send low-volume funnel milestones such as paywall views and onboarding steps to RevenueHog, including the reserved onboarding_step event that powers the funnel.