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

# iOS Web Entitlements

> Real-time subscription unlock for web-purchased subscriptions

<Info>
  **Supported on:** iOS SDK `1.0.61+`
</Info>

Web entitlements enable apps that sell subscriptions via web checkout (e.g., Stripe) to unlock premium features in the native app in real-time. After calling `identify()`, the SDK listens for entitlement changes automatically -- when a user purchases on the web, the app unlocks instantly without a restart.

## How It Works

1. User purchases a subscription on your website (via Stripe or another web payment provider).
2. Your backend writes the entitlement to AppDNA.
3. The SDK detects the change in real-time and updates the local cache.
4. Your app reacts via the callback and unlocks premium features.

<Info>
  Web entitlements are separate from in-app purchase entitlements (StoreKit / Play Billing). They are designed for apps that also sell subscriptions through a web checkout flow.
</Info>

## Check Current Entitlement

After `identify()`, the current web entitlement is available synchronously from cache:

```swift theme={null}
if let entitlement = AppDNA.webEntitlement, entitlement.isActive {
    // User has an active web subscription
    unlockPremium()
    print("Plan: \(entitlement.planName ?? "unknown"), Status: \(entitlement.status)")
    if let periodEnd = entitlement.currentPeriodEnd {
        print("Renews: \(periodEnd)")
    }
}
```

## Listen for Changes

Register a callback to react in real-time when the entitlement status changes:

```swift theme={null}
AppDNA.onWebEntitlementChanged { entitlement in
    if entitlement?.isActive == true {
        unlockPremium()
    } else {
        lockPremium()
    }
}
```

This fires immediately when:

* A new web subscription is purchased
* A subscription renews
* A subscription expires or is canceled
* A subscription enters a grace period

## WebEntitlement

| Property           | Type                | Description                                  |
| ------------------ | ------------------- | -------------------------------------------- |
| `isActive`         | `Bool`              | Whether the subscription is currently active |
| `planName`         | `String?`           | Name of the subscription plan                |
| `priceId`          | `String?`           | Stripe price identifier                      |
| `status`           | `EntitlementStatus` | Current status enum (see below)              |
| `currentPeriodEnd` | `Date?`             | End date of the current billing period       |
| `interval`         | `String?`           | Billing interval (e.g., `"month"`, `"year"`) |
| `trialEnd`         | `Date?`             | Trial expiration date, if in a trial         |

### Entitlement Statuses

| Status     | Description                                                      |
| ---------- | ---------------------------------------------------------------- |
| `active`   | Subscription is active and paid                                  |
| `trialing` | User is in a free trial period                                   |
| `past_due` | Payment failed, in grace period                                  |
| `canceled` | Subscription is canceled (access may continue until period ends) |

## Access

Web entitlements are accessed as a top-level static property on `AppDNA`:

```swift theme={null}
let webEntitlement = AppDNA.webEntitlement
```

## Auto-Tracked Events

| Event                       | Trigger                                 |
| --------------------------- | --------------------------------------- |
| `web_entitlement_activated` | Web subscription becomes active         |
| `web_entitlement_expired`   | Web subscription expires or is canceled |

## Full Example

```swift theme={null}
import AppDNASDK

class PremiumManager {
    func setup() {
        // Listen for web entitlement changes
        AppDNA.onWebEntitlementChanged { [weak self] entitlement in
            DispatchQueue.main.async {
                self?.updateAccessState(entitlement: entitlement)
            }
        }
    }

    func checkAccessOnLaunch() {
        // Check in-app purchase entitlements
        Task {
            let hasIAP = await AppDNA.billing.hasActiveSubscription()
            if hasIAP {
                unlockPremium()
                return
            }
        }

        // Check web entitlement
        if let web = AppDNA.webEntitlement, web.isActive {
            unlockPremium()
            return
        }

        // No active subscription from either source
        lockPremium()
    }

    private func updateAccessState(entitlement: WebEntitlement?) {
        if entitlement?.isActive == true {
            unlockPremium()
        } else {
            // Re-check IAP entitlements before locking
            Task {
                let hasIAP = await AppDNA.billing.hasActiveSubscription()
                if !hasIAP {
                    lockPremium()
                }
            }
        }
    }

    private func unlockPremium() {
        // Enable premium features
    }

    private func lockPremium() {
        // Disable premium features
    }
}
```

<Note>
  Web entitlements require Stripe (or compatible payment provider) integration configured in the Console under **Settings > Billing > Web Payments**. The real-time listener activates automatically after `identify()` is called.
</Note>
