Google Play native integration

Connect your Google Play app to Affiliateo using Real-Time Developer Notifications (RTDN) and the Play Developer API directly. No RevenueCat in the middle.

When to use this

vs RevenueCat or Stripe

Pick this when

  • You already operate a GCP project and are comfortable with Pub/Sub setup
  • You want to avoid third-party SaaS in your payment stack
  • You only ship on Android (or you're combining with Apple native for iOS)

RevenueCat is easier if

  • You also ship on iOS (RC covers both stores with one integration)
  • You don't want to maintain a GCP project + Pub/Sub configuration

Don't use this for

  • Physical goods / services billed outside Play (use Stripe)
  • Web purchases (Play Billing is Android-only)

What you'll need

Google Cloud project

Owned by you, with Pub/Sub enabled. Free tier covers RTDN volume.

Service account JSON key

Created in IAM → Service Accounts → Keys. Grants both Play Developer API access AND Pub/Sub push identity.

Play Console access

Owner / Admin on the app's Play Console listing, to grant the SA permissions and enable RTDN.

Android package name

e.g. com.acme.app. Must match what's live in Play Console.

Setup

Roughly 15 minutes once GCP + Play Console are open.

1
Create the service account. In Google Cloud Console → IAM & Admin → Service Accounts → Create. Give it a name like affiliateo-rtdn. Skip the optional role grants on this screen.
2
Generate a JSON key for it. Open the new SA → Keys → Add Key → JSON. A file downloads. Keep it; you'll paste it into Affiliateo in step 6.
3
Grant the SA Play Developer permissions. In Play Console → Users and permissions → Invite new user. Paste the SA's email (looks like affiliateo-rtdn@your-project.iam.gserviceaccount.com), grant App access > View financial data and View app information on the app you're connecting.
4
Create the Pub/Sub topic. In Cloud Console → Pub/Sub → Topics → Create topic. Name it something like play-rtdn. Topics are free; you'll only pay for delivery volume past the free tier.
5
Grant Play the Publisher role on the topic. On the new topic → Permissions → Add principal. Add google-play-developer-notifications@system.gserviceaccount.com with role Pub/Sub Publisher. This is what lets Google publish RTDN into your topic.
6
Connect in Affiliateo. Back in the app wizard, drop the .json file into the upload field, paste the package name, optionally paste the full topic path (projects/{proj}/topics/{name}), and click Connect Google Play. We'll call voidedpurchases.list to validate the key works and the SA has the right access.
7
Create the Pub/Sub subscription pointing at us. On the topic → Subscriptions → Create.
  • Delivery type: Push
  • Endpoint URL: paste the URL Affiliateo gave you after Connect
  • Enable authentication: ON
  • Service account: pick the same SA you uploaded
  • Audience: leave blank (defaults to the endpoint URL, which is what we expect)
8
Enable RTDN in Play Console. Play Console → your app → Monetize → Monetization setup → Real-time developer notifications. Paste the full topic name (projects/{proj}/topics/{name}). Click Send test notification. Within a minute you should see Affiliateo's connection card flip from "Awaiting first RTDN" to "Receiving RTDN".

How attribution flows

The piece that ties an affiliate referral to a Google purchase.

The Affiliateo SDK mints a stable UUID v4 the first time a user opens your app from an affiliate link. The UUID is persisted on-device so it survives launches, and registered with Affiliateo via POST /api/v1/mobile/google-account-id so the server can map it to the visitor/affiliate.

At purchase time, your code passes the same UUID to Play Billing as obfuscatedAccountId. Google stamps it onto every subsequent RTDN for that purchase chain (initial sale, renewals, refunds) underexternalAccountIdentifiers.obfuscatedExternalAccountId.

When the RTDN arrives, we look up the visitor by UUID in a single indexed query and credit the affiliate. No additional client work beyond setting the field at purchase.

Purchase code

Pass the UUID to Play Billing.

Pass obfuscatedAccountId to BillingFlowParams at purchase time

PurchaseManager.kt
import com.android.billingclient.api.BillingClient
import com.android.billingclient.api.BillingFlowParams
import com.android.billingclient.api.ProductDetails
import com.affiliateo.Affiliateo

fun launchBilling(activity: Activity, product: ProductDetails) {
    val params = BillingFlowParams.newBuilder()
        .setProductDetailsParamsList(listOf(
            BillingFlowParams.ProductDetailsParams.newBuilder()
                .setProductDetails(product)
                .build()
        ))
        // The UUID Affiliateo minted on identify. Stamped onto every RTDN
        // we receive from Google for this user's purchases + renewals.
        .apply {
            Affiliateo.obfuscatedAccountId?.let { setObfuscatedAccountId(it) }
        }
        .build()

    billingClient.launchBillingFlow(activity, params)
}

What we record

RTDN types that produce a conversion row.

SUBSCRIPTION_PURCHASEDsale (subscription)First payment for an auto-renewing sub
SUBSCRIPTION_RENEWEDrenewalLocks to original affiliate + commission rate
SUBSCRIPTION_RECOVEREDrenewalAccount hold cleared; treat as renewal
SUBSCRIPTION_RESTARTEDrenewalUser re-enabled auto-renew after canceling
SUBSCRIPTION_REVOKEDrefundAdmin / customer support refund
ONE_TIME_PRODUCT_PURCHASEDsale (one-time)Recorded with amount 0 (see limitations)
ONE_TIME_PRODUCT_CANCELEDrefundOne-time purchase refunded
VOIDED_PURCHASE (via voidedpurchases.list)refund (reconciliation)Daily backfill in case live RTDN was missed

Other RTDN types (state changes that don't affect money: ON_HOLD, IN_GRACE_PERIOD, EXPIRED, etc.) are deduped at the inbox level but skipped for conversion side effects.

Known limitations

One-time IAP price isn't in Google's API response. purchases.products.get returns purchase state but no monetary amount. We record the conversion with amount_cents=0 and no commission. Subscriptions are unaffected. If you ship one-time IAPs and want them attributed for commission, ping us and we'll wire up a price-lookup workaround.
Currency reporting is native, not normalized. Each conversion stores the price in the actual currency Google charged in. Cross-currency aggregate stats use raw amounts (no FX conversion). Single-currency apps are unaffected.
Pre-existing purchases aren't retroactively attributed. Customers who installed before you wired up the SDK can't carry an obfuscatedAccountId. Those purchases land as organic (no commission) until they purchase again with the SDK active.

Troubleshooting

Connection shows "Awaiting first RTDN" indefinitely

The most common cause is the Pub/Sub subscription not being configured to push to us. In Cloud Console → Pub/Sub → Subscriptions, confirm: delivery is Push, endpoint URL matches what we returned, authentication is enabled with the service account you connected. Then in Play Console, click Send test notification again.

Health banner: "Google credentials invalid"

Your service account key was revoked or expired. In GCP, generate a new JSON key for the same SA and reconnect in the app edit form. Existing data isn't lost; only future RTDN delivery is paused.

Sale didn't attribute to the expected affiliate

The purchase's obfuscatedAccountId didn't match a tracked visitor on this app. Either the SDK wasn't initialized before purchase, the user installed before the SDK was wired up, or the affiliate match window (30 days) expired. Check the visitor row for the user's device id.