Handle flow & paywall events - Unity

This guide covers event handling for purchases, restorations, product selection, and flow rendering. You must also implement button handling (closing the flow, opening links, etc.). See our guide on handling flow actions for details.

Flows and paywalls configured with the Flow Builder or Paywall Builder don’t need extra code to make and restore purchases. However, they generate some events that your app can respond to. Those events include button presses (close buttons, URLs, product selections, and so on) as well as notifications on purchase-related actions. Learn how to respond to these events below.

Want to see a real-world example of how Adapty SDK is integrated into a mobile app? Check out our sample apps, which demonstrate the full setup, including displaying paywalls, making purchases, and other basic functionality.

Handling events

To control or monitor processes occurring on the flow screen within your mobile app, implement the IAdaptyFlowsEventsListener interface and register it with Adapty.SetFlowsEventsListener():

using UnityEngine;
using AdaptySDK;

public class FlowEventsHandler : MonoBehaviour, IAdaptyFlowsEventsListener
{
    void Start()
    {
        Adapty.SetFlowsEventsListener(this);
    }

    // Implement all interface methods below
}

These methods are where you add your custom logic to respond to flow events. The SDK applies no default behavior to them: a successful purchase or an error does not dismiss the view automatically — call view.Dismiss(...) yourself when appropriate.

User-generated events

Flow appeared

Invoked when the flow view is presented on the screen.

On iOS, also invoked when a user taps the web paywall button inside a flow, and a web paywall opens in an in-app browser.

public void FlowViewDidAppear(AdaptyUIFlowView view) { }

Flow disappeared

Invoked when the flow view is dismissed from the screen.

On iOS, also invoked when a web paywall opened from a flow in an in-app browser disappears from the screen.

public void FlowViewDidDisappear(AdaptyUIFlowView view) { }

Product selection

Invoked when a product is selected for purchase (by a user or by the system).

public void FlowViewDidSelectProduct(
    AdaptyUIFlowView view,
    string productId
) { }
Event example (Click to expand)
{
  "productId": "premium_monthly"
}

Started purchase

Invoked when a user initiates the purchase process.

public void FlowViewDidStartPurchase(
    AdaptyUIFlowView view,
    AdaptyPaywallProduct product
) { }

In Observer mode, purchases started from a flow are delivered to your IAdaptyUIObserverModeResolver instead.

Event example (Click to expand)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Successful, canceled, or pending purchase

If purchase succeeds, the user cancels their purchase, or the purchase appears to be pending, this method will be invoked. User cancellations and pending payments (e.g., parental approval required) trigger this method, not FlowViewDidFailPurchase.

The flow stays open after the purchase until you dismiss it, so call view.Dismiss(...) yourself once the user gets access:

public void FlowViewDidFinishPurchase(
    AdaptyUIFlowView view,
    AdaptyPaywallProduct product,
    AdaptyPurchaseResult purchasedResult
) {
    switch (purchasedResult.Type) {
        case AdaptyPurchaseResultType.Success:
            // Check if user has access to premium features
            if (purchasedResult.Profile != null
                && purchasedResult.Profile.AccessLevels.TryGetValue("premium", out var premium)
                && premium.IsActive) {
                view.Dismiss(null);
            }
            break;
        case AdaptyPurchaseResultType.Pending:
            // Handle pending purchase (e.g., user will pay offline with cash)
            break;
        case AdaptyPurchaseResultType.UserCancelled:
            // Handle user cancellation
            break;
        default:
            break;
    }
}
Event examples (Click to expand)
// Successful purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  }
}

// Cancelled purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "UserCancelled"
  }
}

// Pending purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "Pending"
  }
}

We recommend dismissing the flow screen in case of successful purchase.

Failed purchase

If a purchase fails due to an error, this method will be invoked. This includes StoreKit/Google Play Billing errors (payment restrictions, invalid products, network failures), transaction verification failures, and system errors. Note that user cancellations trigger FlowViewDidFinishPurchase with a cancelled result instead, and pending payments do not trigger this method.

public void FlowViewDidFailPurchase(
    AdaptyUIFlowView view,
    AdaptyPaywallProduct product,
    AdaptyError error
) { }
Event example (Click to expand)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  }
}

Started restore

Invoked when a user initiates the restore process:

public void FlowViewDidStartRestore(AdaptyUIFlowView view) { }

Successful restore

Invoked when purchase restoration succeeds. The flow stays open after the restore until you dismiss it:

public void FlowViewDidFinishRestore(
    AdaptyUIFlowView view,
    AdaptyProfile profile
) {
    // Check if user has access to premium features
    if (profile.AccessLevels.TryGetValue("premium", out var premium) && premium.IsActive) {
        view.Dismiss(null);
    }
}
Event example (Click to expand)
{
  "profile": {
    "accessLevels": {
      "premium": {
        "id": "premium",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    },
    "subscriptions": [
      {
        "vendorProductId": "premium_monthly",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    ]
  }
}

We recommend dismissing the screen if the user has the required accessLevel. Refer to the Subscription status topic to learn how to check it.

Failed restore

Invoked when purchase restoration fails:

public void FlowViewDidFailRestore(
    AdaptyUIFlowView view,
    AdaptyError error
) { }
Event example (Click to expand)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Finished web payment navigation

After attempting to open a web paywall for purchase (whether successful or failed), this method will be invoked:

public void FlowViewDidFinishWebPaymentNavigation(
    AdaptyUIFlowView view,
    AdaptyPaywallProduct product,
    AdaptyError error
) { }

Parameters:

  • product: The product for which the web paywall was opened (or attempted), or null
  • error: null if the web paywall opened successfully, or an AdaptyError if it failed
Event examples (Click to expand)
// Successful navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": null
}

// Failed navigation
{
  "product": null,
  "error": {
    "code": "wrong_param",
    "message": "Current method is not available for this product",
    "details": {
      "underlyingError": "Product not configured for web purchases"
    }
  }
}

Data fetching and rendering

Product loading errors

Invoked when product loading fails and provides AdaptyError. If you didn’t pass the product array during initialization, AdaptyUI will retrieve the necessary objects from the server by itself. This operation may fail, and AdaptyUI will report the error by invoking this method:

public void FlowViewDidFailLoadingProducts(
    AdaptyUIFlowView view,
    AdaptyError error
) { }
Event example (Click to expand)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Rendering and runtime errors

If an error occurs during the interface rendering, or another non-purchase runtime error occurs, it will be reported by this method. The view is not dismissed automatically — call view.Dismiss(...) yourself if desired:

public void FlowViewDidReceiveError(
    AdaptyUIFlowView view,
    AdaptyError error
) { }
Event example (Click to expand)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render flow interface",
    "details": {
      "underlyingError": "Invalid flow configuration"
    }
  }
}

In a normal situation, such errors should not occur, so if you come across one, please let us know.

Analytics events

FlowViewDidReceiveAnalyticEvent is reserved for custom analytic events from a flow. Flows don’t emit these to your code yet, so leave the method body empty — IAdaptyFlowsEventsListener is a C# interface, so the method still has to be present:

public void FlowViewDidReceiveAnalyticEvent(
    AdaptyUIFlowView view,
    string name,
    IDictionary<string, object> @params
) { }

Handle system requests

The IAdaptyUISystemRequestsHandler (registered via Adapty.SetSystemRequestsHandler(...)) is reserved for system requests from a flow: OS permission prompts (such as push notifications or camera access) and app review requests. Flows don’t trigger these requests yet, so you don’t need to register a handler.

Android system back button

The Android system back button (or back gesture) is delivered to FlowViewDidPerformAction as a SystemBack action and does not dismiss the flow on its own — the user leaves the flow through a path you define, such as a Close button or an on_device_back action in the builder. If you want the system back button to dismiss the flow, handle the action yourself:

public void FlowViewDidPerformAction(
    AdaptyUIFlowView view,
    AdaptyUIUserAction action
) {
    switch (action.Type) {
        case AdaptyUIUserActionType.Close:
        case AdaptyUIUserActionType.SystemBack:
            view.Dismiss(null);
            break;
        default:
            // handle other events
            break;
    }
}

See the guide on handling flow actions for the full list of actions.

This guide covers event handling for purchases, restorations, product selection, and paywall rendering. You must also implement button handling (closing paywall, opening links, etc.). See our guide on handling button actions for details.

Paywalls configured with the Paywall Builder don’t need extra code to make and restore purchases. However, they generate some events that your app can respond to. Those events include button presses (close buttons, URLs, product selections, and so on) as well as notifications on purchase-related actions taken on the paywall. Learn how to respond to these events below.

This guide is for new Paywall Builder paywalls only which require Adapty SDK v3.3.0 or later.

Want to see a real-world example of how Adapty SDK is integrated into a mobile app? Check out our sample apps, which demonstrate the full setup, including displaying paywalls, making purchases, and other basic functionality.

Handling events

To control or monitor processes occurring on the paywall screen within your mobile app, implement the AdaptyPaywallsEventsListener interface:

using UnityEngine;
using AdaptySDK;

public class PaywallEventsHandler : MonoBehaviour, AdaptyPaywallsEventsListener
{
    void Start()
    {
        Adapty.SetPaywallsEventsListener(this);
    }

    // Implement all required interface methods below
}

User-generated events

Paywall appeared

Invoked when the paywall view is presented on the screen.

On iOS, also invoked when a user taps the web paywall button inside a paywall, and a web paywall opens in an in-app browser.

public void PaywallViewDidAppear(AdaptyUIPaywallView view) { }

Paywall disappeared

Invoked when the paywall view is dismissed from the screen.

On iOS, also invoked when a web paywall opened from a paywall in an in-app browser disappears from the screen.

public void PaywallViewDidDisappear(AdaptyUIPaywallView view) { }

Product selection

Invoked when a product is selected for purchase (by a user or by the system).

public void PaywallViewDidSelectProduct(
    AdaptyUIPaywallView view, 
    string productId
) { }
Event example (Click to expand)
{
  "productId": "premium_monthly"
}

Started purchase

Invoked when a user initiates the purchase process.

public void PaywallViewDidStartPurchase(
    AdaptyUIPaywallView view, 
    AdaptyPaywallProduct product
) { }
Event example (Click to expand)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  }
}

Successful, canceled, or pending purchase

If purchase succeeds, the user cancels their purchase, or the purchase appears to be pending, this method will be invoked. User cancellations and pending payments (e.g., parental approval required) trigger this method, not PaywallViewDidFailPurchase.

public void PaywallViewDidFinishPurchase(
    AdaptyUIPaywallView view, 
    AdaptyPaywallProduct product, 
    AdaptyPurchaseResult purchasedResult
) { }
Event examples (Click to expand)
// Successful purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "Success",
    "profile": {
      "accessLevels": {
        "premium": {
          "id": "premium",
          "isActive": true,
          "expiresAt": "2024-02-15T10:30:00Z"
        }
      }
    }
  }
}

// Cancelled purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "UserCancelled"
  }
}

// Pending purchase
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "purchaseResult": {
    "type": "Pending"
  }
}

We recommend dismissing the screen in that case.

Failed purchase

If a purchase fails due to an error, this method will be invoked. This includes StoreKit/Google Play Billing errors (payment restrictions, invalid products, network failures), transaction verification failures, and system errors. Note that user cancellations trigger PaywallViewDidFinishPurchase with a cancelled result instead, and pending payments do not trigger this method.

public void PaywallViewDidFailPurchase(
    AdaptyUIPaywallView view, 
    AdaptyPaywallProduct product, 
    AdaptyError error
) { }
Event example (Click to expand)
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "purchase_failed",
    "message": "Purchase failed due to insufficient funds",
    "details": {
      "underlyingError": "Insufficient funds in account"
    }
  }
}

Started restore

Invoked when a user initiates the restore process:

public void PaywallViewDidStartRestore(AdaptyUIPaywallView view) { }

Successful restore

Invoked when purchase restoration succeeds:

public void PaywallViewDidFinishRestore(
    AdaptyUIPaywallView view, 
    AdaptyProfile profile
) { }
Event example (Click to expand)
{
  "profile": {
    "accessLevels": {
      "premium": {
        "id": "premium",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    },
    "subscriptions": [
      {
        "vendorProductId": "premium_monthly",
        "isActive": true,
        "expiresAt": "2024-02-15T10:30:00Z"
      }
    ]
  }
}

We recommend dismissing the screen if the user has the required accessLevel. Refer to the Subscription status topic to learn how to check it.

Failed restore

Invoked when purchase restoration fails:

public void PaywallViewDidFailRestore(
    AdaptyUIPaywallView view, 
    AdaptyError error
) { }
Event example (Click to expand)
{
  "error": {
    "code": "restore_failed",
    "message": "Purchase restoration failed",
    "details": {
      "underlyingError": "No previous purchases found"
    }
  }
}

Finished web payment navigation

After attempting to open a web paywall for purchase (whether successful or failed), this method will be invoked:

public void PaywallViewDidFinishWebPaymentNavigation(
    AdaptyUIPaywallView view, 
    AdaptyPaywallProduct product, 
    AdaptyError error
) { }

Parameters:

  • product: The product for which the web paywall was opened (or attempted)
  • error: null if the web paywall opened successfully, or an AdaptyError if it failed
Event examples (Click to expand)
// Successful navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": null
}

// Failed navigation
{
  "product": {
    "vendorProductId": "premium_monthly",
    "localizedTitle": "Premium Monthly",
    "localizedDescription": "Premium subscription for 1 month",
    "localizedPrice": "$9.99",
    "price": 9.99,
    "currencyCode": "USD"
  },
  "error": {
    "code": "wrong_param",
    "message": "Current method is not available for this product",
    "details": {
      "underlyingError": "Product not configured for web purchases"
    }
  }
}

Data fetching and rendering

Product loading errors

Invoked when product loading fails and provides AdaptyError. If you didn’t pass the product array during initialization, AdaptyUI will retrieve the necessary objects from the server by itself. This operation may fail, and AdaptyUI will report the error by invoking this method:

public void PaywallViewDidFailLoadingProducts(
    AdaptyUIPaywallView view, 
    AdaptyError error
) { }
Event example (Click to expand)
{
  "error": {
    "code": "products_loading_failed",
    "message": "Failed to load products from the server",
    "details": {
      "underlyingError": "Network timeout"
    }
  }
}

Rendering errors

Invoked when an error occurs during interface rendering and provides AdaptyError:

public void PaywallViewDidFailRendering(
    AdaptyUIPaywallView view, 
    AdaptyError error
) { }
Event example (Click to expand)
{
  "error": {
    "code": "rendering_failed",
    "message": "Failed to render paywall interface",
    "details": {
      "underlyingError": "Invalid paywall configuration"
    }
  }
}

In a normal situation, such errors should not occur, so if you come across one, please let us know.