---
id: "integrations/attribution/appstack"
title: "Appstack"
description: "With our Appstack integration you can:"
permalink: "/docs/integrations/attribution/appstack"
slug: "appstack"
version: "current"
original_source: "docs/integrations/attribution/appstack.mdx"
---

> **AI agents:** This is the Markdown version of a RevenueCat documentation page. For the complete documentation index, see [llms.txt](https://www.revenuecat.com/docs/llms.txt).

With our Appstack integration you can:

- Attribute subscription revenue to campaigns tracked by Appstack using the RevenueCat SDK.
- Identify users in Appstack using the `$appstackId` Customer 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](https://www.revenuecat.com/docs/integrations/attribution/provider-comparison).

## Before you begin

Review [Getting Started with Attribution Integrations](https://www.revenuecat.com/docs/integrations/attribution/getting-started) for how attribution integrations fit together, then use the [setup checklist](https://www.revenuecat.com/docs/integrations/attribution/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](#optional-send-campaign-data-to-revenuecat).
- Call `setAppstackAttributionParams()` after `Purchases.configure` and before the first purchase or paywall load whenever possible.
- For missing `$appstackId` or campaign fields on Customer Profiles or Charts, see [Attribution Troubleshooting](https://www.revenuecat.com/docs/integrations/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](https://docs.appstack.tech/) 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](https://www.revenuecat.com/docs/customers/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:

1. Configure the RevenueCat SDK.
2. Get attribution params from the Appstack SDK with `getAttributionParams()`.
3. Merge `getAppstackId()` into the map as `appstack_id`.
4. 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.
5. 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

- `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 from `getAttributionParams()`.
- The AdSupport framework is required to access the IDFA parameter. In
  Xcode, add `AdSupport.framework` to your app target under **Frameworks,
  Libraries, and Embedded Content**, leave it set to **Do Not Embed**, then
  import `AdSupport` in your Swift file.
- On Appstack iOS SDK 4.5.0+, `getAttributionParams()` includes
  `appstack_match_status` (`matched`, `matched_no_params`, `organic`,
  `skipped`, `failed`, or `not_configured`). `matched_no_params` means the
  install matched without campaign keys for partners. Only `failed` is
  typically worth retrying later.

#### Android

- Android does not currently expose `appstack_match_status` on
  `getAttributionParams()`. An empty or ID-only map still means campaign
  keys may arrive later or not at all.

**Swift**

```swift
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
}
```

**Kotlin**

```kotlin
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 */ }
    }
)
```

**Swift (async/await)**

```swift
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
}
```

**Kotlin (Coroutines)**

```kotlin
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
}
```

:::tip[Recommended: merge the local Appstack ID as a safety net]
`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](https://www.revenuecat.com/docs/customers/customer-attributes). Campaign attributes are additive to `$appstackId`, not a replacement for it.

:::warning[Set final campaign values only]
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.

1. In Appstack, open **Integrations** → **RevenueCat**, then copy the **webhook URL** and **authorization header**.
2. Go to your RevenueCat dashboard and select your project.
3. In the lower-left corner, select **Integrations**.
4. Select **Appstack**.
5. Paste the Appstack **Webhook URL**.
6. 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](https://www.revenuecat.com/docs/integrations/attribution/setup-checklist#configure-provider-communication).

| 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.

:::note[Sandbox events]
RevenueCat sends sandbox events to Appstack with `environment` set to `SANDBOX`. Appstack separates them from production events automatically, so you can verify webhook delivery end to end before releasing your app.
:::

1. Confirm required attribution data from [Send attribution data to RevenueCat](#2-send-attribution-data-to-revenuecat) is present in the [Customer Profile](https://www.revenuecat.com/docs/dashboard-and-metrics/customer-profile#customer-details). If you expect campaign fields, confirm `$ad`, `$campaign`, or related attributes are present — not only `$appstackId`.
2. Make a sandbox purchase with a new customer.
3. If webhook forwarding is enabled, open the sandbox purchase event in [Customer History](https://www.revenuecat.com/docs/dashboard-and-metrics/customer-profile#customer-history) and confirm the Appstack delivery row exists. If it doesn't, see [no provider delivery row troubleshooting](https://www.revenuecat.com/docs/integrations/attribution/troubleshooting#no-provider-delivery-row-appears-in-revenuecat).
4. Check Appstack reporting tools after any expected provider-side delay.

:::success[You've done it!]
You should start seeing Appstack attribution data on RevenueCat customers, and Appstack webhook events when forwarding is enabled.
:::
