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

# Presenting Offers

> Present Encore retention offers to users at key moments in your app.

## Overview

Encore allows you to present targeted offers to users in exchange for rewards (free trials, discounts, credits) at critical moments like cancellation flows or feature paywalls. The SDK uses a delegation pattern — you register purchase and passthrough handlers, then call `.show()` wherever you need an offer.

***

## Present Offers

Presenting an Encore offer is a single line. The facade is `@MainActor`, and `show()` presents on the main thread automatically:

```swift theme={null}
Button("Cancel Subscription") {
    Encore.shared.placement("cancellation_flow").show()   // fire-and-forget
}
```

<Warning>
  When presenting offers in critical flows (like cancellation), your `onPassthrough` handler ensures users are never blocked from completing their intended action.
</Warning>

<Info>
  Register handlers before presenting. See [Handle Offer Results](#handle-offer-results) below.
</Info>

### Observing the outcome

The fire-and-forget `show()` above hands control to your registered handlers. If you'd rather branch on the outcome at the call site, use the async form — it returns a [`PresentationResult`](../sdk-reference/presentation-result). Because a SwiftUI `Button` action is synchronous, wrap the `await` in a `Task`:

```swift theme={null}
Button("Cancel Subscription") {
    Task {
        do {
            let result = try await Encore.shared.placement("cancellation_flow").show()
            switch result {
            case .granted(let entitlement):
                // Success: the user earned an entitlement. Reflect it in your UI.
                unlockPremium(for: entitlement)
            case .notGranted(let reason):
                // Dismiss, no offers, experiment control, or unsupported OS.
                proceedWithCancellation(reason: reason)
            }
        } catch {
            // Transport / SDK error — never block the user.
            proceedWithCancellation(reason: nil)
        }
    }
}
```

<Note>
  `show()` only **throws** for genuine transport/SDK failures. A user dismissal is `.notGranted(...)`, not a thrown error.
</Note>

***

## Handle Offer Results

<Tip>
  **Choosing your integration shape**: This page shows the handler pattern. If your call site has direct access to purchase logic, the async-result pattern is often cleaner. See [Integration Patterns](/concepts/integration-patterns) for the decision tree and [iOS Integration Patterns](/publishers/ios/guides/integration-patterns) for Swift examples.
</Tip>

Register handlers that tell the SDK how to complete purchases and what to do when a user dismisses. Register these once at app launch, after `configure()`.

### Register `onPurchaseRequest`

Called when a user accepts an offer. Use this to trigger a purchase via StoreKit, RevenueCat, Adapty, or your own billing implementation. **Return `true` on success** — Encore then auto-dismisses the offer sheet. Return `false` (or throw) to keep the sheet up so the user can retry.

```swift theme={null}
Encore.shared.onPurchaseRequest { purchaseRequest in
    guard let product = try await Product.products(for: [purchaseRequest.productId]).first else {
        return false
    }
    let result = try await product.purchase()
    guard case .success(let verification) = result,
          case .verified(let transaction) = verification else {
        return false  // cancelled, pending, or unverified — keep the sheet up
    }
    await transaction.finish()
    return true  // verified purchase — dismiss the sheet
}
```

<Tip>
  The example above uses StoreKit 2. See [onPurchaseRequest()](../sdk-reference/on-purchase-request) for RevenueCat, Adapty, Qonversion, and custom subscription manager examples.
</Tip>

### Register `onPassthrough`

Called when the user dismisses the offer or no offers are available. Use this to resume the user's original action (e.g., proceed with cancellation).

```swift theme={null}
Encore.shared.onPassthrough { placementId in
    // Resume your original user flow
}
```

### `onPurchaseComplete` (Optional)

Only needed when you don't set an `onPurchaseRequest` handler, or when integrating with subscription managers that don't auto-detect purchases (e.g., Adapty, Qonversion). RevenueCat and Superwall auto-detect purchases and don't need this callback.

The handler receives the verified StoreKit `transaction` and the `productId` (in that order):

```swift theme={null}
Encore.shared.onPurchaseComplete { transaction, productId in
    // Sync the StoreKit transaction with your subscription manager
}
```

### Summary

| Callback             | When it fires                                           | Required? |
| -------------------- | ------------------------------------------------------- | --------- |
| `onPurchaseRequest`  | User accepts the offer                                  | Yes       |
| `onPassthrough`      | User dismisses or no offers available                   | Yes       |
| `onPurchaseComplete` | Purchase finishes (for managers that don't auto-detect) | Optional  |

<Info>
  Both `onPurchaseRequest` and `onPassthrough` must be registered before presenting any placements.
</Info>

***

## Third-Party Paywall Integrations

For Superwall and RevenueCat integrations, see the dedicated guides:

<CardGroup cols={2}>
  <Card title="Using Superwall" icon="layers" href="/publishers/ios/guides/using-superwall">
    Trigger Encore from Superwall paywalls and dismissals
  </Card>

  <Card title="Using RevenueCat" icon="layers" href="/publishers/ios/guides/using-revenuecat">
    Trigger Encore from RevenueCat paywalls and dismissals
  </Card>
</CardGroup>

***

## What a Grant Looks Like

When a user accepts an offer, Encore grants the entitlement in **two phases**: a **provisional** grant for instant UX (visible immediately), then a **verified** grant once the backend confirms the transaction. Provisional grants can later be revoked if verification fails.

Gate reversible UI (unlocking a screen, hiding an upsell) on the broad `.all` scope; gate irreversible or expensive grants on `.verified`. See [EntitlementScope — Two-phase entitlements](../sdk-reference/entitlement-scope#two-phase-entitlements-provisional-vs-verified) for the full model.

***

## Next Steps

* [Add Subscription Product](./add-subscription-product) - Create an App Store subscription product and connect it to Encore
* [Configure Analytics](./configure-analytics) - Track offer performance and measure impact
