Logging Conversions

Learn how to capture user context and log conversions to optimize upsell decisions.

Optimizing conversions begins with tracking them. In ContextDecision, this is done by capturing the user’s context and logging whether a conversion occurred.

Our SDK provides different methods for capturing context, each designed for specific use cases. This document outlines these methods and explains when to use them.

If you’re new here, refer to the instructions at Getting Started for guidance on generating your license key and installing the SDK.

Capturing Context

ContextManager.swift
static func fetchContext(
    flowName: String,
    duration: Int,
    customSignals: [CustomSignal] = [],
    callback: @escaping ((Context) -> Void)
)

This method allows you to capture the user context asynchronously. This is the recommended method to be used in most scenarios.

When calling this method, you provide:

  • A flow name, which uniquely identifies the context being captured. Each flow should have a distinct name, even if the same flow is triggered from different parts of the app. We automatically analyze the overlap between different flows, to decide if there should be one, or more models.

    • Best practice: use snake_case and group flows that lead to the same prompt using the same prefix, e.g. upsell_onboarding, upsell_first_action .

  • A duration, which determines how long accelerometer and gyroscope data is collected, in seconds. This value must be between 2 and 7 seconds, with a recommended default of 3 seconds.

  • An optional array of custom signals, which allows you to append additional contextual information relevant to your flow or app. Learn more in Custom Signals.

Once the context is ready, the callback executes asynchronously on the main thread. The execution timing depends on your app’s state:

  • If the app has been active and in the foreground for at least 3 seconds (or your configured duration), the callback executes instantly.

  • If the app was recently launched or resumed from the background, it may take up to 3 seconds (or your configured duration) for the context to be available.

Logging Outcomes

An outcome indicates whether an offer led to a conversion (e.g., a purchase, an ad click) or was dismissed. Always log at least one outcome for each captured context to help train the ML models effectively.

For in-app purchases, we require the product that was purchased to be passed as an argument to the outcome log. See Revenue Outcomes for more details.

If your offer is a paywall with a "Restore Purchases" button, see The skipped outcome.

Decision-Making

The context object returned by fetchContext includes a shouldUpsell property, which determines whether an upsell offer should be shown. During the calibration phase, this property always returns true. Once a ML model is deployed to the flow, it starts making real-time decisions. If the model determines that it's a bad time, shouldUpsell will be false, so you can not show the paywall, and thus log skipped as the outcome.

There are exceptions, such as the Multivariate Monetization use case, where you would show an ad (not a paywall) when shouldUpsell is false. In these cases, you shouldn't log the skipped outcome, but instead log the outcome of your ad interaction.

For more details about this use case, see Multivariate Monetization.

Usage Example

To use this method, show the upsell offer inside the callback block to ensure the context is evaluated before presenting the offer. This guarantees that the decision logic runs first, preventing unnecessary offers from being shown when the conditions are not met:

MyOnboardingViewController.swift
// 1. Capture the context
ContextManager.fetchContext(flowName: "upsell_onboarding") { [weak self] context in
    // 2. Check if shouldUpsell is true
    guard context.shouldUpsell else {
        context.log(.skipped) // Log skipped when shouldUpsell is false
        return
    }
    // 3. Create the offer view controller
    let vc = MyPremiumOfferViewController()
    vc.userDidPurchase = { product in
        context.logRevenueOutcome(from: product)
    }
    vc.userDidDismiss = {
        context.log(.negative)
    }
    // 4. Show offer view controller here
    // …
}

If your project does not favor closure-based implementations like shown above when logging outcomes, see Retrieving an Existing Context below for an alternative approach that better fits different architectures.

Retrieving an Existing Context

Not all architectures use closures for inter-view-controller communication. In such cases, you can capture the context and present the offer in one place, and log the outcome separately. Use the recentContext(flowName:) method to retrieve a previously captured context for a given flow name.

Note: This method does not capture a new context — it only retrieves an existing one. If no context has been captured for the specified flow, this method returns nil.

Usage Example

MyOnboardingViewController.swift
ContextManager.fetchContext(flowName: "upsell_onboarding") { [weak self] context in
    guard context.shouldUpsell else {
        context.log(.skipped)
        return
    }
    self?.present(MyPremiumOfferViewController(), animated: true)
}
MyPremiumOfferViewController.swift
func userDidCompletePurchase(product: Product) {
    if let context = ContextManager.recentContext(flowName: "upsell_onboarding") {
        context.logRevenueOutcome(from: product)
    } else {
        // This is an error state. Make sure the context above is created first.
    }
}

func dismissViewController() {
    if let context = ContextManager.recentContext(flowName: "upsell_onboarding") {
        context.log(.negative)
    } else {
        // This is an error state. Make sure the context above is created first.
    }
    // Dismiss your view controller here
}

Last updated

Was this helpful?