---
title: Monitor checkout lifecycle
description: >-
  Register callbacks for checkout start, live updates, completion, failure, and
  dismissal, and respond to link taps with a link policy.
source_url:
  html: >-
    https://shopify.dev/docs/storefronts/mobile-next/checkout-kit/monitor-checkout-lifecycle
  md: >-
    https://shopify.dev/docs/storefronts/mobile-next/checkout-kit/monitor-checkout-lifecycle.md
---

# Monitor checkout lifecycle

When a buyer moves through checkout, your app needs to respond: mirror live totals, record the order on completion, surface failures, and clean up when the buyer leaves. Checkout Kit delivers all of this through five lifecycle events with the same meaning on every platform, each expressed idiomatically — a delegate on UIKit, view modifiers in SwiftUI, builder callbacks on Android, and per-present callbacks on React Native.

***

## Requirements

* [Checkout Kit](https://shopify.dev/docs/storefronts/mobile/checkout-kit) installed in your app.
* A valid `checkoutUrl` from [Build a mobile storefront](https://shopify.dev/docs/storefronts/mobile/build-mobile-storefront) or a [cart permalink](https://shopify.dev/docs/apps/build/checkout/create-cart-permalinks).

***

## The five lifecycle events

| Event | When it fires | Payload |
| - | - | - |
| `start` | When checkout becomes ready for the buyer. Events that arrive before your presentation callbacks are bound — including during a background preload — aren't replayed, so `start` isn't guaranteed when you present a preloaded checkout. | The initial `Checkout` snapshot |
| `update` | Whenever checkout state changes: line items, totals, messages, or delivery. Consecutive identical snapshots are suppressed. | A full `Checkout` snapshot, not a diff |
| `complete` | When the buyer places the order. Completion isn't deduplicated, so make what you do here idempotent for the checkout or order. | The final `Checkout` snapshot |
| `fail` | When checkout can't continue. Checkout closes itself afterwards and `dismiss` follows. Can fire before `start`. `complete` is authoritative: if a `fail` arrives after it — for example the web content process is killed while the confirmation page is still open — the order was still placed, so don't undo what you recorded on `complete`. | An error with a stable `code` |
| `dismiss` | When the checkout presentation closes, whatever the outcome: the buyer closed it, a terminal failure closed it (`fail` fires first), or the buyer dismissed the confirmation page after completing. It doesn't fire when your app dismisses checkout programmatically, so put cleanup next to your dismiss call too, and keep it idempotent. | None |

Each `update` carries the complete current state, so you never need to accumulate diffs: replace your copy of the checkout with the new snapshot. Recoverable problems — a validation message, a declined payment the buyer can retry — are checkout state, not failures; they arrive as `update` events while the buyer resolves them in checkout.

***

## Register lifecycle callbacks

## Register lifecycle callbacks

##### Swift

```swift
extension MyViewController: CheckoutDelegate {
  func checkoutDidStart(_ event: CheckoutStartEvent) {
    // Checkout is ready for the buyer.
  }

  func checkoutDidUpdate(_ event: CheckoutUpdateEvent) {
    // Full snapshot: mirror totals or line items into your UI.
    renderTotals(event.checkout.totals)
  }

  func checkoutDidComplete(_ event: CheckoutCompleteEvent) {
    // The buyer placed the order. Record it and reset cart state.
    recordOrder(event.checkout)
  }

  func checkoutDidFail(_ event: CheckoutFailureEvent) {
    // Checkout can't continue and closes itself; checkoutDidDismiss() follows.
    handleCheckoutError(event.error)
  }

  func checkoutDidDismiss() {
    // The surface closed. Don't reset cart state here — completion
    // is signaled only by checkoutDidComplete.
  }
}
```

##### Swift (SwiftUI)

```swift
ShopifyCheckout(checkout: url)
  .onStart { event in
    // Checkout is ready for the buyer.
  }
  .onUpdate { event in
    renderTotals(event.checkout.totals)
  }
  .onComplete { event in
    recordOrder(event.checkout)
    showCheckout = false
  }
  .onFail { event in
    handleCheckoutError(event.error)
    showCheckout = false
  }
  .onDismiss {
    showCheckout = false
  }
```

##### Kotlin

```kotlin
ShopifyCheckout.present(checkoutUrl, activity) {
    onStart { event ->
        // Checkout is ready for the buyer.
    }
    onUpdate { event ->
        // Full snapshot: mirror totals or line items into your UI.
        renderTotals(event.checkout.totals)
    }
    onComplete { event ->
        // The buyer placed the order. Record it and reset cart state.
        recordOrder(event.checkout)
    }
    onFail { event ->
        // Checkout can't continue and closes itself; onDismiss follows.
        handleCheckoutError(event.error)
    }
    onDismiss {
        // The surface closed. Completion is signaled only by onComplete.
    }
}
```

##### Kotlin (listener)

```kotlin
// Java-friendly alternative: extend DefaultCheckoutListener so you
// inherit the permission, file chooser, and geolocation defaults.
val listener = object : DefaultCheckoutListener() {
    override fun onCheckoutStarted(event: CheckoutStartEvent) { }
    override fun onCheckoutUpdated(event: CheckoutUpdateEvent) { renderTotals(event.checkout.totals) }
    override fun onCheckoutCompleted(event: CheckoutCompleteEvent) { recordOrder(event.checkout) }
    override fun onCheckoutFailed(event: CheckoutFailureEvent) { handleCheckoutError(event.error) }
    override fun onCheckoutDismissed() { }
}
ShopifyCheckout.present(checkoutUrl, activity, listener)
```

##### React Native

```javascript
const shopifyCheckout = useShopifyCheckout();

shopifyCheckout.present(checkoutUrl, {
  onStart: ({checkout}) => {
    // Checkout is ready for the buyer.
  },
  onUpdate: ({checkout}) => {
    renderTotals(checkout.totals);
  },
  onComplete: ({checkout}) => {
    recordOrder(checkout);
  },
  onFail: ({error}) => {
    // Checkout can't continue and closes itself; onDismiss follows.
    handleCheckoutError(error);
  },
  onDismiss: () => {
    // Checkout closed, whatever the outcome.
  },
});
```

**Note:**

The dismiss event fires when the buyer closes checkout, whether or not they completed the purchase. Only the complete event signals success — never reset cart state on dismiss alone.

When your app presents `ShopifyCheckout` in its own SwiftUI `.sheet`, `.onDismiss` runs after the buyer taps the close button or swipes the sheet down, and after a terminal failure closes checkout. It doesn't run when the buyer taps outside the sheet (for example on iPad) or when your app sets the sheet's `isPresented` binding to `false`, because SwiftUI owns that presentation. To handle every dismissal, run your cleanup from the sheet's own `onDismiss` parameter, such as `.sheet(isPresented: $isPresented, onDismiss: refreshCart)`, and keep it safe to run twice. Add `.interactiveDismissDisabled()` to `ShopifyCheckout` to stop dismissal by tapping outside the sheet; this also turns off swipe-down.

***

## Handle failures

The failure event carries an error with a stable, machine-readable `code` — the same set of codes on every platform (string values on iOS and React Native, a `CheckoutErrorCode` enum on Android), such as `cart_expired`, `cart_completed`, `invalid_cart`, `storefront_password_required`, `http_error`, `network_error`, and `sdk_error`. Recoverability is decided inside checkout: by the time `fail` fires, checkout can't continue. Checkout closes itself and `dismiss` follows. To retry, record the intent in the failure handler and present a fresh `checkoutUrl` after `dismiss`; presenting again from the failure handler is ignored while checkout is still closing. The exception is a failure before any presentation opens, such as an unsupported System WebView or a non-HTTPS URL on Android: only `fail` fires, `present()` returns `null`, and there's no `dismiss` to wait for, so handle those in the failure callback directly.

***

## Respond to link taps

When the buyer taps a link inside checkout — terms of service, a policy page, an external app — Checkout Kit opens it for you by default (a Safari view controller or `UIApplication.open` on iOS, Custom Tabs or an intent on Android). To intercept links, return a link action from the link callback:

* **Open**: let the kit open the link (the default).
* **Handled**: your app routed the link itself; the kit does nothing.
* **Cancel**: don't open the link.

## Respond to link taps

##### Swift

```swift
// UIKit: implement checkoutAction(for:) on the delegate.
func checkoutAction(for link: CheckoutLink) -> CheckoutLinkAction {
  if link.url.host == "help.example.com" {
    presentInAppBrowser(link.url)
    return .handled
  }
  return .default
}

// SwiftUI equivalent:
// ShopifyCheckout(checkout: url).onLinkClick { link in .default }
```

##### Kotlin

```kotlin
ShopifyCheckout.present(checkoutUrl, activity) {
    onLinkClick { link ->
        if (link.url.host == "help.example.com") {
            presentInAppBrowser(link.url)
            CheckoutLinkAction.Handled
        } else {
            CheckoutLinkAction.Default
        }
    }
}
```

***

## Handle file uploads, geolocation, and permissions on Android

iOS handles file uploads, geolocation, and permissions automatically. React Native handles geolocation prompts on both platforms; on React Native Android, file inputs and other permission requests fall back to the Android defaults (no file chooser, permission requests denied) unless you add a native integration. On Android, handle them in the presentation builder (or override the same methods on `DefaultCheckoutListener`):

## Android-specific callbacks

```kotlin
ShopifyCheckout.present(checkoutUrl, activity) {
    onShowFileChooser { webView, filePathCallback, fileChooserParams ->
        // Handle HTML file input elements in checkout.
        // To cancel, call filePathCallback.onReceiveValue(null) and return true.
        true
    }
    onGeolocationPermissionsShowPrompt { origin, callback ->
        // Handle geolocation requests, for example "Use my location" for pickup points.
    }
    onGeolocationPermissionsHidePrompt {
        // Dismiss the geolocation permissions prompt.
    }
    onPermissionRequest { permissionRequest ->
        // Handle permission requests from the checkout WebView, such as camera access.
    }
}
```

**Caution:**

On Android 11+, opening external links requires a `queries` element in your `AndroidManifest.xml` declaring which apps your app interacts with. See the [Android queries documentation](https://developer.android.com/guide/topics/manifest/queries-element).

***

## The Checkout snapshot

The `start`, `update`, and `complete` events all carry the same `Checkout` type: the identifier and `status`, `currency`, `lineItems`, `totals`, `discounts`, `messages`, `fulfillment`, `links`, and — on completion — the `order` confirmation. Buyer and payment details are redacted by default; see [Privacy compliance](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance) for what event payloads contain and how the enhanced buyer events scope changes that.

Checkout Kit doesn't relay web pixel analytics events. Derive journey signal from the lifecycle events — `start`, the `update` stream, and `complete` — and keep conversion analytics server-side. If you're migrating an integration that consumed pixel events, see [Upgrade from Checkout Sheet Kit](https://shopify.dev/docs/storefronts/mobile-next/checkout-kit/upgrade-to-v4).

***

## Next steps

[Configure checkout branding\
\
](https://shopify.dev/docs/storefronts/mobile/checkout-kit/configuration)

[Choose between storefront branding and app-matched theming.](https://shopify.dev/docs/storefronts/mobile/checkout-kit/configuration)

[Privacy compliance\
\
](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance)

[Collect consent and understand what buyer data event payloads contain.](https://shopify.dev/docs/storefronts/mobile/checkout-kit/privacy-compliance)

[Upgrade from Checkout Sheet Kit\
\
](https://shopify.dev/docs/storefronts/mobile-next/checkout-kit/upgrade-to-v4)

[Move an existing v3 integration to the v4 event model.](https://shopify.dev/docs/storefronts/mobile-next/checkout-kit/upgrade-to-v4)

***
