Appstack
Integrate RevenueCat with Appstack for attribution and targeting
With our Appstack integration you can:
- Attribute subscription revenue to campaigns tracked by Appstack using the RevenueCat SDK.
- Identify users in Appstack using the
$appstackIdCustomer Attribute. - Send RevenueCat subscription events directly to your Appstack webhook endpoint (optional).
Integration at a glance
| Revenue support | Supports Negative Revenue | Sends Sandbox Events | Includes Customer Attributes | Sends Transfer Events | Optional Event Types |
|---|---|---|---|---|---|
| ✅ | ✅ | ❌ | ❌ | ✅ | ❌ |
For a cross-provider view of identifiers, revenue support, sandbox behavior, and attribution responsibilities, see the attribution provider comparison.
Before you begin
Review Getting Started with Attribution Integrations for how attribution integrations fit together, then use the setup checklist as a reference for required lifecycle events, reporting choices, duplicate-event risks, and testing checks.
The following considerations are specific to Appstack:
- The SDK attribution mapping (steps 1–2) and webhook event forwarding (step 3) are independent. You can use attribution mapping without configuring webhook forwarding, or enable both.
- Campaign attributes on RevenueCat customers come only from the on-device params map. Appstack dashboard attribution does not guarantee those fields are in RevenueCat. See (Optional) Send campaign data to RevenueCat.
- Call
setAppstackAttributionParams()afterPurchases.configureand before the first purchase or paywall load whenever possible. - For missing
$appstackIdor campaign fields on Customer Profiles or Charts, see Attribution Troubleshooting.
1. Install the Appstack SDK
Before RevenueCat can integrate with Appstack, your app must be running the Appstack SDK. Refer to the Appstack developer documentation for the latest installation instructions.
2. Send attribution data to RevenueCat
Appstack attribution data below is used for Customer Profiles, Charts, targeting, and — when webhook event forwarding is configured — matching events to Appstack users. The same setAppstackAttributionParams() call can also set campaign attribution attributes, click IDs, and device identifiers when those keys are present. You do not need to call collectDeviceIdentifiers() separately.
| Key | Description | Required |
|---|---|---|
$appstackId | The unique user identifier assigned by Appstack | ✅ |
These properties can be set manually, like any other Customer Attributes, or through setAppstackAttributionParams() with the map from Appstack's getAttributionParams(). Set them after the RevenueCat SDK is configured and before the first purchase occurs whenever possible. Repeated calls do not repair purchases that RevenueCat already processed without the attributes.
The examples below show the recommended app-side flow:
- Configure the RevenueCat SDK.
- Get attribution params from the Appstack SDK with
getAttributionParams(). - Merge
getAppstackId()into the map asappstack_id. - Pass the map to
setAppstackAttributionParams(). The call syncs attributes and fetches fresh offerings before returning, so Appstack-based targeting is applied before you display a paywall. - Call
setAppstackAttributionParams()again when attribution or device identifiers become available later, such as after App Tracking Transparency permission is granted or Appstack returns updated params.
- iOS
- Android
- None
getAttributionParams()is async and must be awaited.- If you request App Tracking Transparency permission to access the IDFA,
call
setAppstackAttributionParams()again after the customer grants permission, passing a fresh params map fromgetAttributionParams(). - The AdSupport framework is required to access the IDFA parameter. In
Xcode, add
AdSupport.frameworkto your app target under Frameworks, Libraries, and Embedded Content, leave it set to Do Not Embed, then importAdSupportin your Swift file. - On Appstack iOS SDK 4.5.0+,
getAttributionParams()includesappstack_match_status(matched,matched_no_params,organic,skipped,failed, ornot_configured).matched_no_paramsmeans the install matched without campaign keys for partners. Onlyfailedis typically worth retrying later.
- Android does not currently expose
appstack_match_statusongetAttributionParams(). An empty or ID-only map still means campaign keys may arrive later or not at all.
- Swift
- Kotlin
- Swift (async/await)
- Kotlin (Coroutines)
import AppstackSDK // Provides AppstackAttributionSdk
import RevenueCat
import AdSupport // Required for IDFA collection
// ...
Purchases.configure(withAPIKey: "public_sdk_key")
// ...
// Retrieve attribution params from the Appstack SDK
let base = await AppstackAttributionSdk.shared.getAttributionParams() ?? [:]
let params: [String: Any]
if let id = AppstackAttributionSdk.shared.getAppstackId() {
params = base.merging(["appstack_id": id]) { _, new in new }
} else {
params = base
}
// Forward to RevenueCat — syncs attributes and fetches fresh offerings
// so Appstack-based targeting is applied before the callback returns.
Purchases.shared.attribution.setAppstackAttributionParams(params) { offerings, error in
// Use `offerings` to present the correct paywall for this user
}
import com.appstack.attribution.AppstackAttributionSdk // Provides AppstackAttributionSdk
import com.revenuecat.purchases.Offerings
import com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.PurchasesError
import com.revenuecat.purchases.interfaces.SyncAttributesAndOfferingsCallback
// ...
Purchases.configure(this, "public_sdk_key")
// ...
// Retrieve attribution params from the Appstack SDK
val base = AppstackAttributionSdk.getAttributionParams() ?: emptyMap()
// Merge the locally-cached Appstack ID as a safety net in case the
// network-based getAttributionParams() didn't return it.
val params = AppstackAttributionSdk.getAppstackId()?.let {
base + ("appstack_id" to it)
} ?: base
// Forward to RevenueCat — syncs attributes and fetches fresh offerings
// so Appstack-based targeting is applied before the callback returns.
Purchases.sharedInstance.setAppstackAttributionParams(
params,
object : SyncAttributesAndOfferingsCallback {
override fun onSuccess(offerings: Offerings) {
// Use `offerings` to present the correct paywall for this user
}
override fun onError(error: PurchasesError) { /* handle error */ }
}
)
import AppstackSDK // Provides AppstackAttributionSdk
import RevenueCat
import AdSupport // Required for IDFA collection
// ...
Purchases.configure(withAPIKey: "public_sdk_key")
// ...
// Retrieve attribution params from the Appstack SDK
let base = await AppstackAttributionSdk.shared.getAttributionParams() ?? [:]
let params: [String: Any]
if let id = AppstackAttributionSdk.shared.getAppstackId() {
params = base.merging(["appstack_id": id]) { _, new in new }
} else {
params = base
}
// Forward to RevenueCat — syncs attributes and fetches fresh offerings
// so Appstack-based targeting is applied before the await returns.
do {
let offerings = try await Purchases.shared.attribution.setAppstackAttributionParams(params)
// Use `offerings` to present the correct paywall for this user
} catch {
// handle error
}
import com.appstack.attribution.AppstackAttributionSdk // Provides AppstackAttributionSdk
import com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.PurchasesException
import com.revenuecat.purchases.awaitSetAppstackAttributionParams
// ...
Purchases.configure(this, "public_sdk_key")
// ...
// Retrieve attribution params from the Appstack SDK
val base = AppstackAttributionSdk.getAttributionParams() ?: emptyMap()
// Merge the locally-cached Appstack ID as a safety net in case the
// network-based getAttributionParams() didn't return it.
val params = AppstackAttributionSdk.getAppstackId()?.let {
base + ("appstack_id" to it)
} ?: base
// Forward to RevenueCat — syncs attributes and fetches fresh offerings
// so Appstack-based targeting is applied before the suspend function returns.
try {
val offerings = Purchases.sharedInstance.awaitSetAppstackAttributionParams(params)
// Use `offerings` to present the correct paywall for this user
} catch (e: PurchasesException) {
// handle error
}
getAttributionParams() fetches attribution data over the network and should include appstack_id. Transient network issues can leave the ID out of the response. The snippets above merge in getAppstackId(), which reads the ID from local storage and does not depend on the network.
(Optional) Send campaign data to RevenueCat
RevenueCat isn't an attribution network and can't determine which ad drove an install or conversion. When Appstack returns final campaign values in getAttributionParams(), setAppstackAttributionParams() maps them to reserved Customer Attributes. Campaign attributes are additive to $appstackId, not a replacement for it.
Don't set temporary placeholders like Organic, Unknown, or No User Consent while waiting for final attribution. Reserved attribution attributes can only be set once per customer. If Appstack later returns final campaign values, RevenueCat can't replace a placeholder you already set.
RevenueCat only stores those fields from the on-device params map. Appstack can show campaign or ad attribution in its dashboard even when getAttributionParams() returns only an Appstack ID — for example when matching runs server-side. If you need campaign fields in Charts, Customer Profiles, or Appstack-based targeting, confirm keys like appstack_ad and appstack_campaign are present before purchase, and call setAppstackAttributionParams() again when Appstack returns updated params. If attribution is resolved outside the SDK, push the final values into RevenueCat from your app when you have them.
| Key | Description |
|---|---|
$mediaSource | The attribution source or network |
$campaign | The campaign name or identifier |
$adGroup | The ad group name or identifier |
$ad | The ad name or identifier |
$keyword | The keyword associated with the attribution |
3. Configure Appstack in RevenueCat
Webhook event forwarding is optional. After your app sends attribution data to RevenueCat, you can enable the integration so RevenueCat delivers subscription lifecycle events to Appstack.
- In Appstack, open Integrations → RevenueCat, then copy the webhook URL and authorization header.
- Go to your RevenueCat dashboard and select your project.
- In the lower-left corner, select Integrations.
- Select Appstack.
- Paste the Appstack Webhook URL.
- Paste the Appstack Authorization Header.
After the integration is active in RevenueCat, it can take 30–60 minutes to appear as active in Appstack.
Event types
RevenueCat forwards the subscription lifecycle events below to Appstack. There are no optional event types to configure in the RevenueCat Appstack settings. For shared lifecycle definitions, see the setup checklist.
| RevenueCat Event |
|---|
| Initial Purchase |
| Renewal |
| Cancellation |
| Uncancellation |
| Non-Renewing Purchase |
| Subscription Paused |
| Expiration |
| Billing Issue |
| Product Change |
| Transfer |
| Subscriber Alias |
4. Test the Appstack integration
Before rolling out the integration, test with a new customer after the SDK, customer attributes, and (if you enabled webhook forwarding) dashboard settings are configured.
Appstack event forwarding does not send sandbox events. To verify webhook delivery end to end, use a production purchase after $appstackId is present on the customer.
- Confirm required attribution data from Send attribution data to RevenueCat is present in the Customer Profile. If you expect campaign fields, confirm
$ad,$campaign, or related attributes are present — not only$appstackId. - Make a production purchase with a new customer (or use Appstack's recommended production-safe test path).
- If webhook forwarding is enabled, open the purchase event in Customer History and confirm the Appstack delivery row exists. If it doesn't, see no provider delivery row troubleshooting.
- Check Appstack reporting tools after any expected provider-side delay.
You should start seeing Appstack attribution data on RevenueCat customers, and Appstack webhook events when forwarding is enabled.