> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appstack.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# RevenueCat

<Prompt description="Use Cursor, Claude Code, or another AI to help you implement the RevenueCat integration with Appstack." actions={["copy", "cursor"]}>
  You are an expert mobile monetization engineer helping me integrate RevenueCat with the Appstack SDK. You are running inside an IDE assistant such as Cursor or Claude Code and you can see my codebase.

  Use the reference below to wire the integration for my platform (Swift, Kotlin, React Native, or Flutter). When I paste this prompt and share my code, you should:

  1. Show exactly where to call `setAppstackAttributionParams()` in my app lifecycle and produce ready-to-paste code for my platform, using the snippets below.
  2. Ensure both the Appstack attribution params and the Appstack ID are assembled into the `params` map and forwarded to RevenueCat exactly as described.
  3. Confirm that `setAppstackAttributionParams()` is called after `Purchases.configure` and before the first paywall load, and that the returned offerings are used immediately.
  4. Propose concrete edits that minimize duplication and keep the integration in one clear place in my code.
  5. Give me a short checklist of what you configured (IDs, attributes, lifecycle placement, ATT handling if iOS) so I can see that everything from the docs is covered.

  ***

  ## Reference: RevenueCat + Appstack integration

  **RevenueCat SDK requirements**

  Use a RevenueCat SDK version that includes `setAppstackAttributionParams()`:

  | Platform     | Package                  | Minimum version |
  | :----------- | :----------------------- | :-------------- |
  | iOS          | `purchases-ios`          | `5.61.0`        |
  | Android      | `purchases-android`      | `9.23.0`        |
  | React Native | `react-native-purchases` | `9.12.0`        |
  | Flutter      | `purchases-flutter`      | `9.14.0`        |

  **1. Call `setAppstackAttributionParams()` after `Purchases.configure`**

  Build the `params` map from both `getAttributionParams()` and `getAppstackId()`, then pass it to RevenueCat. This single call sets `$appstackId`, campaign attribution attributes, click IDs, and device identifiers, and it syncs attributes while fetching fresh offerings before returning.

  ```swift theme={null}
  // Swift
  Purchases.configure(withAPIKey: "public_sdk_key")

  Task {
      let base = await AppstackAttributionSdk.shared.getAttributionParams() ?? [:]
      var params = base
      if let id = AppstackAttributionSdk.shared.getAppstackId() {
          params["appstack_id"] = id
      }
      Purchases.shared.attribution.setAppstackAttributionParams(params) { offerings, error in
          // Use `offerings` to present the correct paywall for this user
      }
  }
  ```

  ```kotlin theme={null}
  // Kotlin
  Purchases.configure(this, "public_sdk_key")

  fun syncRevenueCatAttribution() {
      val base = AppstackAttributionSdk.getAttributionParams()
      val params = base.toMutableMap()
      AppstackAttributionSdk.getAppstackId()?.let { params["appstack_id"] = it }

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

  // Call syncRevenueCatAttribution() after Appstack initialization and before loading offerings or paywalls.
  ```

  ```typescript theme={null}
  // React Native
  Purchases.configure({ apiKey: "public_sdk_key" });

  const base = (await AppstackSDK.getAttributionParams()) ?? {};
  const params = { ...base };
  const id = await AppstackSDK.getAppstackId();
  if (id != null) {
    params["appstack_id"] = id;
  }
  const offerings = await Purchases.setAppstackAttributionParams(params);
  // Use `offerings` to present the correct paywall for this user
  ```

  ```dart theme={null}
  // Flutter
  await Purchases.configure(PurchasesConfiguration("public_sdk_key"));

  final base = await AppstackPlugin.getAttributionParams() ?? <String, dynamic>{};
  final params = Map<String, dynamic>.from(base);
  final id = await AppstackPlugin.getAppstackId();
  if (id != null) {
    params['appstack_id'] = id;
  }
  final Offerings offerings = await Purchases.setAppstackAttributionParams(params);
  // Use `offerings` to present the correct paywall for this user
  ```

  **2. iOS App Tracking Transparency (iOS 14.5+)**

  * If you request ATT permission to access the IDFA, call `setAppstackAttributionParams()` again after the customer grants permission, rebuilding `params` from the latest `getAttributionParams()` and `getAppstackId()` values. The `AdSupport` framework is required to collect the IDFA on iOS.

  **3. Credentials in dashboards (non-code steps)**

  * In Appstack: `Integrations` → `RevenueCat` → copy **webhook URL** and **authorization header**.

  * In RevenueCat: `Integrations` → `Attribution` → `Appstack` → paste **webhook URL** and **authorization header**.

  * Once active on RevenueCat's platform, it can take **30–60 minutes** to appear active in Appstack.
</Prompt>

With the RevenueCat integration, you can:

1. Use RevenueCat in-app events to run enhanced app campaigns.
2. Unlock attribution-based paywall optimization.

<Note>
  **To successfully connect RevenueCat, you must:**

  1. Have Owner/Admin access to an Appstack organization.
  2. Have access to the correct RevenueCat account.
</Note>

## Requirements

Use a RevenueCat SDK version that includes `setAppstackAttributionParams()`:

| Platform     | Package                  | Minimum version |
| :----------- | :----------------------- | :-------------- |
| iOS          | `purchases-ios`          | `5.61.0`        |
| Android      | `purchases-android`      | `9.23.0`        |
| React Native | `react-native-purchases` | `9.12.0`        |
| Flutter      | `purchases-flutter`      | `9.14.0`        |

## Connect to RevenueCat

Follow the steps to ensure the RevenueCat integration works:

<Steps>
  <Step title="SDK configuration">
    After configuring the Purchases SDK and before the first offerings or paywall load, call `setAppstackAttributionParams()` with the attribution data from the Appstack SDK. This single call sets the `$appstackId`, campaign attribution attributes (`$mediaSource`, `$campaign`, `$adGroup`, `$ad`, `$keyword`), click IDs, and device identifiers — no need to call `collectDeviceIdentifiers()` separately.

    The call also syncs attributes to the RevenueCat backend and fetches fresh offerings before returning, so Appstack-based targeting is applied before your paywall loads.

    <Note>
      On iOS, check `appstack_match_status` in the params before drawing conclusions from an empty-looking result. `failed` means the match has not resolved yet, so it is worth rebuilding `params` and calling again later. `not_configured` means the call ran before `configure(...)` finished — fix the ordering — or that the SDK is disabled, in which case retrying never helps.
    </Note>

    <Note>
      Learn more about the RevenueCat x Appstack integration by [reading this article.](https://www.revenuecat.com/docs/integrations/attribution/appstack)
    </Note>

    <CodeGroup>
      ```swift Swift theme={null}
      import AdSupport
      // ...
      Purchases.configure(withAPIKey: "public_sdk_key")
      // ...

      Task {
          let base = await AppstackAttributionSdk.shared.getAttributionParams() ?? [:]
          var params = base
          if let id = AppstackAttributionSdk.shared.getAppstackId() {
              params["appstack_id"] = id
          }

          // 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 theme={null}
      // ...
      Purchases.configure(this, "public_sdk_key")
      // ...

      fun syncRevenueCatAttribution() {
          val base = AppstackAttributionSdk.getAttributionParams()
          val params = base.toMutableMap()
          AppstackAttributionSdk.getAppstackId()?.let { params["appstack_id"] = it }

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

      // Call syncRevenueCatAttribution() after Appstack initialization and before loading offerings or paywalls.
      ```

      ```typescript React Native theme={null}
      // ...
      Purchases.configure({ apiKey: "public_sdk_key" });
      // ...

      const base = (await AppstackSDK.getAttributionParams()) ?? {};
      const params = { ...base };
      const id = await AppstackSDK.getAppstackId();
      if (id != null) {
        params["appstack_id"] = id;
      }

      // Forward to RevenueCat — syncs attributes and fetches fresh offerings
      // so Appstack-based targeting is applied before the promise resolves.
      const offerings = await Purchases.setAppstackAttributionParams(params);
      // Use `offerings` to present the correct paywall for this user
      ```

      ```dart Flutter theme={null}
      // ...
      await Purchases.configure(PurchasesConfiguration("public_sdk_key"));
      // ...

      final base = await AppstackPlugin.getAttributionParams() ?? <String, dynamic>{};
      final params = Map<String, dynamic>.from(base);
      final id = await AppstackPlugin.getAppstackId();
      if (id != null) {
        params['appstack_id'] = id;
      }

      // Forward to RevenueCat — syncs attributes and fetches fresh offerings
      // so Appstack-based targeting is applied before the await returns.
      final Offerings offerings = await Purchases.setAppstackAttributionParams(params);
      // Use `offerings` to present the correct paywall for this user
      ```
    </CodeGroup>

    <Note>
      **RevenueCat SDK minimum versions**

      `setAppstackAttributionParams()` is available in `purchases-ios` 5.61.0+, `purchases-android` 9.23.0+, `react-native-purchases` 9.12.0+, and `purchases-flutter` 9.14.0+. Earlier versions do not include the Appstack integration method.
    </Note>

    <Note>
      **iOS App Tracking Transparency (iOS 14.5+)**

      If you request App Tracking permission through ATT to access the IDFA, call `setAppstackAttributionParams()` again after the customer grants permission, rebuilding `params` from the latest `getAttributionParams()` and `getAppstackId()` values as in the code examples above. The `AdSupport` framework is required to collect the IDFA on iOS.
    </Note>
  </Step>

  <Step title="Copy the credentials">
    1. In Appstack, from the side menu, select **Integrations** > **RevenueCat.**
    2. Copy the **webhook URL.**
    3. Copy the **authorization header.**
  </Step>

  <Step title="Paste the credentials">
    1. In RevenueCat, from the dashboard, go to **Integrations** > **Attribution** > **Appstack.**
    2. Paste the **webhook URL.**
    3. Paste the **authorization header.**

    <Note>
      After the integration is active on RevenueCat's platform, it can take 30-60 minutes to appear as active on Appstack's side.
    </Note>
  </Step>
</Steps>

<Warning>
  The integration will show an error if less than 50% of events received over the last 24 hours include an Appstack ID. For RevenueCat, pass this under the `appstack_id` key when building `params`. When this threshold is not met, these events cannot be used in ad network integration pages.
</Warning>

## List of events

Below is a list of events you can forward to ad networks to optimize your campaigns.

| Name                            | Definition                                                                                                                                                                                                                                                                   |
| :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rc_trial_started`              | Fired when a user begins a free trial. Triggered on the initial purchase when the period type is `TRIAL`.                                                                                                                                                                    |
| `rc_trial_qualified`            | Fired when a free trial is still active two hours after it started — meaning the user did not cancel within the qualification window.                                                                                                                                        |
| `rc_trial_converted`            | Fired when a free trial successfully converts to a paid subscription. This happens on the first renewal after the trial period ends.                                                                                                                                         |
| `rc_intro_started`              | Fired when a user begins an intro offer — a paid trial at a discounted price (e.g., \$0.99 for the first week). Triggered on the initial purchase when the period type is `INTRO`                                                                                            |
| `rc_trial_intro_started`        | Fired when either a free trial starts (`rc_trial_started`) or an intro offer starts (`rc_intro_started`). The event triggers as soon as at least one of these conditions is met.                                                                                             |
| `rc_trial_intro_qualified`      | Fired when either a free trial qualifies (`rc_trial_qualified`) or an intro offer qualifies (`rc_intro_qualified`). The event triggers as soon as at least one of these conditions is met.                                                                                   |
| `rc_initial_purchase`           | Fired when any of the following occur: a free trial starts (`rc_trial_started`), an intro offer starts (`rc_intro_started`), or a full-price subscription starts (`rc_subscription_started`). The event triggers as soon as at least one of these conditions is met.         |
| `rc_initial_purchase_qualified` | Fired when any of the following occur: a trial qualified happens (`rc_trial_qualified`), an intro offer starts (`rc_intro_started`), or a full-price subscription starts (`rc_subscription_started`). The event triggers as soon as at least one of these conditions is met. |
| `rc_subscription_started`       | Fired when a user starts a paid subscription at full price, with no trial or intro offer involved. Triggered on the initial purchase when the period type is `NORMAL`                                                                                                        |
| `rc_subscription_renewed`       | Fired on each successful renewal of an active subscription. Indicates the user was billed again and remains subscribed for another period.                                                                                                                                   |
| `rc_in_app_purchase`            | Fired when a user makes a one-time, non-subscription purchase — any product that is not an auto-renewing subscription (e.g., consumables or non-consumable in-app purchases). Unlike subscription events, this purchase does not renew automatically.                        |

## Troubleshooting

### **General tips**

1. Check SDK versions: confirm the RevenueCat SDK includes `setAppstackAttributionParams()` (`purchases-ios` 5.61.0+, `purchases-android` 9.23.0+, `react-native-purchases` 9.12.0+, `purchases-flutter` 9.14.0+). Earlier versions do not expose the method.
2. Check the ordering: call `setAppstackAttributionParams(...)` **after** `Purchases.configure` and **before** the first offerings or paywall load. Use the returned `offerings` to present the paywall rather than fetching them again.
3. Build the full `params` map: include **both** `getAttributionParams()` and `getAppstackId()` (stored under the `appstack_id` key). Passing only one of them, or a wrong key name, breaks attribution.
4. Handle ATT on iOS: if you request App Tracking permission, call `setAppstackAttributionParams(...)` again after the customer grants it, rebuilding `params` from fresh SDK values. The `AdSupport` framework must be linked to collect the IDFA.
5. Allow time for activation: after the integration is active in RevenueCat, it can take **30–60 minutes** to appear active in Appstack.
6. Contact support: if issues persist, reach out with specific error messages at [support@appstack.tech](mailto:support@appstack.tech).

### Common issues

| Issue                                                           | How to fix                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| :-------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| "Less than 50% of events include an Appstack ID" error          | 1. Confirm the app version that includes the Appstack SDK is already released on the store and that more than 24 hours have passed, so enough live installs have reported events.<br />2. Confirm `getAppstackId()` is added to `params` under the `appstack_id` key.<br />3. Confirm `setAppstackAttributionParams(...)` runs on every app start after `Purchases.configure`.<br />4. Allow up to 24 hours for coverage to recover once the fix is rolled out. Below this threshold, events cannot be used in ad network integration pages. |
| `setAppstackAttributionParams` not found / does not compile     | 1. Update the RevenueCat SDK to the minimum version listed above.<br />2. Confirm the method is called on the correct object (`Purchases.shared.attribution` on iOS, `Purchases.sharedInstance` on Android).                                                                                                                                                                                                                                                                                                                                 |
| CocoaPods reports a version conflict on a RevenueCat dependency | Something in your `Podfile` (or another pod) pins that dependency explicitly. The React Native and Flutter wrappers already pin the exact version they need, so remove your own pin and let the wrapper resolve it — do not try to force a different version.                                                                                                                                                                                                                                                                                |
| Offerings or paywall not reflecting attribution                 | 1. Confirm the call runs **before** the first paywall load.<br />2. Use the `offerings` returned by the callback/await instead of a separate fetch.<br />3. Confirm the `params` map is populated (it is available after `configure`).                                                                                                                                                                                                                                                                                                       |
| IDFA missing on iOS                                             | 1. Link the `AdSupport` framework.<br />2. Request ATT permission, then call `setAppstackAttributionParams(...)` again with fresh values.<br />3. Test on a physical device — the IDFA is not available in the simulator.                                                                                                                                                                                                                                                                                                                    |
| Integration not showing as active in Appstack                   | 1. Re-check the **webhook URL** and **authorization header** pasted in RevenueCat match Appstack.<br />2. Wait 30–60 minutes after activation.                                                                                                                                                                                                                                                                                                                                                                                               |

<Prompt description="Use Cursor, Claude Code, or another AI to validate your existing RevenueCat \+ Appstack integration." actions={["copy", "cursor"]}>
  You are an expert mobile monetization engineer reviewing my existing RevenueCat + Appstack integration. You are running inside an IDE assistant such as Cursor or Claude Code and you can see my codebase.

  Your goal is to **validate that my RevenueCat integration fully matches the documentation** and that event and attribution data can flow correctly between Appstack and RevenueCat. When I paste this prompt and share my platform-specific code and configuration, you should:

  1. **SDK and initialization checks**
     * Confirm that:
       * The Appstack SDK is installed and initialized correctly for the platform (Swift, Kotlin, React Native, or Flutter).
       * The RevenueCat SDK version supports Appstack: `purchases-ios` 5.61.0+, `purchases-android` 9.23.0+, `react-native-purchases` 9.12.0+, or `purchases-flutter` 9.14.0+.
       * The RevenueCat `Purchases` SDK is configured via `Purchases.configure` before any attribution calls are made.
     * Point out any missing dependencies, incorrect initialization order, or cases where `setAppstackAttributionParams()` is called before `Purchases.configure`.

  2. **`setAppstackAttributionParams` integration**
     * Locate calls to:
       * `Purchases.shared.attribution.setAppstackAttributionParams(params)` (Swift)
       * `Purchases.sharedInstance.setAppstackAttributionParams(params, ...)` (Kotlin)
       * `Purchases.setAppstackAttributionParams(params)` (React Native)
       * `Purchases.setAppstackAttributionParams(params)` (Flutter)
     * Verify that:
       * The call appears **after** `Purchases.configure` and **before** the first paywall load.
       * The returned `offerings` object is used to present the paywall (not fetched again in a separate call).
       * The call is not redundantly repeated on every screen — it should happen once at startup (and once more after ATT permission is granted on iOS).
     * Propose concrete code changes if the call is missing, misplaced, or incorrectly parameterized.

  3. **Attribution params and Appstack ID**
     * Confirm that the `params` map is built from **both**:
       * `getAttributionParams()` / `AppstackAttributionSdk.getAttributionParams()` / `AppstackSDK.getAttributionParams()` / `AppstackPlugin.getAttributionParams()`
       * `getAppstackId()` stored under the key `"appstack_id"`
     * Flag any integration that passes only one of these two sources, or uses a wrong key name.
     * Remind me that the integration is disabled when less than 50% of events over the last 24 hours include an Appstack ID, and that this coverage only stabilizes once the app build with the Appstack SDK has been live on the store for more than 24 hours.

  4. **iOS App Tracking Transparency handling**
     * Check whether ATT permission is requested in the app.
     * If it is, verify that `setAppstackAttributionParams()` is called a **second time** after the user grants permission, rebuilding `params` from fresh SDK values.
     * Confirm the `AdSupport` framework is linked for IDFA collection.

  5. **Validation report & checklist**
     * Produce a clear report summarizing:
       * What is correctly implemented and safe for production.
       * What is missing, misconfigured, or risky, with platform-specific file names and snippets to change.
     * End with a **RevenueCat-specific checklist** (SDK initialization order, `setAppstackAttributionParams` placement, params map completeness, offerings usage, ATT re-call on iOS, dashboard credentials) that I can use to confirm my integration fully matches the RevenueCat integration guide.
</Prompt>
