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

# onPassthrough()

> Handle not-granted outcomes (dismiss, no offers)

Registers a handler invoked for all not-granted outcomes — when the user dismisses the offer or no offers are available. This signals "Encore didn't result in a purchase — run your original button logic."

<Warning>
  Set `onPassthrough` before calling `placement().show()`. If not set, the SDK will log a warning.
</Warning>

## Signature

```swift theme={null}
@discardableResult
public func onPassthrough(_ handler: @escaping (String?) -> Void) -> Encore
```

## Parameters

| Name      | Type                | Description                                                                                                                                                                                         |
| --------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `handler` | `(String?) -> Void` | Callback receiving the optional `placementId` that resolved as not-granted. Execute your original button logic here. The ID is `nil` when the placement was created without an explicit identifier. |

## Returns

`@discardableResult` — returns the `Encore` instance for chaining.

## When It Fires

Passthrough fires for **not-granted business outcomes**, where Encore didn't result in a purchase but the user should continue. It does **not** fire for transport/SDK errors — those are *thrown* from `try await placement(_:).show()`; catch them there and run your fallback in the `catch` block.

| Scenario                                                    | Fires?                                                    |
| ----------------------------------------------------------- | --------------------------------------------------------- |
| User dismisses the sheet (close, swipe, tap-outside)        | Yes                                                       |
| No offer available for this user                            | Yes                                                       |
| User is in the experiment Control cohort                    | Yes                                                       |
| Device can't present the sheet (iOS 15–16, `unsupportedOS`) | Yes                                                       |
| User accepts an offer                                       | No — see [onPurchaseRequest()](./on-purchase-request)     |
| Transport / SDK error (network, decoding, not configured)   | No — thrown from `show()` as an [`EncoreError`](./errors) |

## Usage

### Cancel flow passthrough

```swift theme={null}
Encore.shared.onPassthrough { placementId in
    // Encore didn't intercept — proceed with original action
    switch placementId {
    case "cancel_flow":
        proceedWithCancellation()
    case "downgrade_flow":
        proceedWithDowngrade()
    default:
        print("Passthrough for \(placementId ?? "unidentified placement")")
    }
}
```

### Chained with onPurchaseRequest

```swift theme={null}
Encore.shared
    .onPurchaseRequest { request in
        guard let product = try await Product.products(for: [request.productId]).first else {
            return false
        }
        let result = try await product.purchase()
        if case .success = result { return true }
        return false
    }
    .onPassthrough { placementId in
        proceedWithCancellation()
    }
```

<Tip>
  Use `onPassthrough` to ensure your original button logic still runs when Encore doesn't intercept. For example, if a user taps "Cancel Subscription" and Encore has no offers, the passthrough handler lets you proceed with the cancellation flow.
</Tip>
