Skip to main content
The iOS WhopIAP SDK is coming soon. This documentation is a preview.
The WhopIAP SDK tracks memberships at both the device level and user level. This allows purchases before login while syncing across devices once users authenticate.

Checking Membership Status

Check Any Subscription

Use isSubscribed() to check if the user has access to any of your configured products:
struct ContentView: View {
    @Environment(WhopIAP.self) var whop

    var body: some View {
        if whop.isSubscribed() {
            PremiumContentView()
        } else {
            PaywallView()
        }
    }
}
func isSubscribed() -> Bool
Returns: true if the user has at least one active membership to any product in productIds.Note: Only checks products passed to configure(productIds:).

Check Specific Product

Use hasAccess(to:) when you have multiple products with different access levels:
struct FeatureView: View {
    @Environment(WhopIAP.self) var whop

    var body: some View {
        VStack {
            // Basic features - check basic product
            if whop.hasAccess(to: "prod_basic") {
                BasicFeaturesView()
            }

            // Pro features - check pro product
            if whop.hasAccess(to: "prod_pro") {
                ProFeaturesView()
            }
        }
    }
}
The product ID must be included in productIds when calling configure(). Checking access to a product not in that array always returns false.
func hasAccess(to productId: String) -> Bool
Parameters:
  • productId: The product ID to check (starts with prod_)
Returns: true if the user has an active membership for that specific product.

Accessing Memberships Directly

For more granular control, access the memberships array:
struct MembershipsView: View {
    @Environment(WhopIAP.self) var whop

    var body: some View {
        List(whop.memberships) { membership in
            VStack(alignment: .leading) {
                Text(membership.productId)
                    .font(.headline)
                Text("Status: \(membership.status)")
                    .foregroundStyle(.secondary)
                if let expiresAt = membership.expiresAt {
                    Text("Expires: \(expiresAt.formatted())")
                        .font(.caption)
                }
            }
        }
    }
}
PropertyTypeDescription
idStringMembership ID
productIdStringAssociated product ID
statusMembershipStatus.active, .cancelled, .expired
expiresAtDate?When the membership expires
createdAtDateWhen the membership was created

User Login

When a user logs in to your app, call logIn() to associate their account with any device-level purchases:
func handleLogin(userId: String) async {
    do {
        try await whop.logIn(appUserId: userId)
        // Memberships now synced to this user
    } catch {
        print("Login failed: \(error)")
    }
}

What Happens on Login

1

Device memberships are claimed

Any purchases made before login are associated with the user’s account.
2

User memberships are loaded

Existing memberships from other devices are synced to this device.
3

Views update automatically

Since WhopIAP is @Observable, any views using isSubscribed() or memberships refresh automatically.
struct LoginView: View {
    @Environment(WhopIAP.self) var whop
    @State private var isLoggingIn = false

    var body: some View {
        Button("Log In") {
            Task {
                isLoggingIn = true
                defer { isLoggingIn = false }

                // Your app's login flow
                let userId = try await performYourLogin()

                // Tell WhopIAP about the logged-in user
                try await whop.logIn(appUserId: userId)
            }
        }
        .disabled(isLoggingIn)
    }
}
func logIn(appUserId: String) async throws
Parameters:
  • appUserId: Your app’s unique identifier for this user. Should not change for the same user.
Throws: WhopIAPError.tokenUnavailable if token fetch fails.Note: User IDs should be stable. Don’t use session tokens or values that change on each login.

User Logout

When users log out, call logOut() to clear user-specific data:
func handleLogout() {
    whop.logOut()
    // User is now logged out
    // isSubscribed() checks device-level memberships only
}
After logout, isSubscribed() still returns true if there are device-level memberships that haven’t been claimed by any user. This allows “guest” purchases to persist.
func logOut()
Clears the current user session. Device-level memberships remain accessible.

Guest Purchases

Users can purchase without logging in. The SDK tracks these purchases by device:
struct GuestPurchaseView: View {
    @Environment(WhopIAP.self) var whop

    var body: some View {
        VStack {
            if whop.isSubscribed() {
                Text("You have premium access!")

                if whop.appUserId == nil {
                    // Prompt to create account
                    Text("Create an account to sync across devices")
                    Button("Sign Up") {
                        // Your signup flow
                    }
                }
            } else {
                Button("Purchase") {
                    Task {
                        try? await whop.purchase("plan_xxxxxxxxxxxxxx")
                    }
                }
            }
        }
    }
}

How Guest Purchases Work

ScenarioBehavior
Purchase without loginMembership tied to device ID
Login after purchaseMembership claimed by user account
Same user, new deviceMemberships sync after login
LogoutDevice memberships remain; user memberships hidden

Device ID

The SDK automatically manages a unique device identifier stored in the iOS Keychain:
// Access the device ID if needed
let deviceId = whop.deviceId

Checking Current User

Access the currently logged-in user ID:
struct ProfileView: View {
    @Environment(WhopIAP.self) var whop

    var body: some View {
        if let userId = whop.appUserId {
            Text("Logged in as: \(userId)")
            Button("Log Out") {
                whop.logOut()
            }
        } else {
            Text("Not logged in")
            Button("Log In") {
                // Your login flow
            }
        }
    }
}

FAQ

When they log in on either device, both device-level memberships are claimed by their account. The next time they log in on the other device, all memberships sync.
Yes. When users log in with logIn(appUserId:), use the same user ID you have in your existing system. Their memberships (if purchased through Whop) will sync automatically.
Avoid changing user IDs. If you must, the old user retains their memberships and the new user ID starts fresh.
Call logOut() when switching accounts, then logIn(appUserId:) with the new user. Each user’s memberships load independently.

Quick Reference

Method/PropertyPurpose
isSubscribed()Check access to any configured product
hasAccess(to:)Check access to specific product
membershipsArray of active memberships
logIn(appUserId:)Associate user with device purchases
logOut()Clear user session
appUserIdCurrent logged-in user ID (or nil)
deviceIdUnique device identifier

Next Steps