RevenueHogdocs
SDKs

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:

  1. On first launch, generate a UUID, persist it and use $anon_<uuid> as appUserId.
  2. Attribute every verified purchase under that anonymous ID as it happens.
  3. When the user logs in, call identify with the real user ID, then re-send attribute for 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 as jws whenever you have it (VerificationResult.jwsRepresentation, or jwsRepresentationIos in 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

SignalEffect
Device token (Bearer dt_…)The write is attested. The user row is marked attested.
No tokenThe write is stored unverified, if the bundle ID resolves to exactly one app.
Valid jwsRevenueHog 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 jwsIgnored, 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.

Last updated on

On this page