SDK
iOS

iOS SDK

Native Swift SDK for presenting AgentWallie paywalls in iOS apps. Renders paywall schemas using SwiftUI and handles purchases via StoreKit 2.

Minimum deployment target: iOS 16.0+

Installation

Swift Package Manager

Add the package URL in Xcode:

https://github.com/cynisca/AgentWallieKit.git

Or add to your Package.swift:

dependencies: [
    .package(url: "https://github.com/cynisca/AgentWallieKit.git", from: "0.1.0")
]

Then add AgentWallieKit to your target's dependencies.

Configuration

Configure the SDK as early as possible in your app lifecycle, typically in your App struct or AppDelegate:

import AgentWallieKit
 
@main
struct MyApp: App {
    init() {
        AgentWallie.configure(apiKey: "pk_your_public_key")
    }
 
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

With Options

var options = AgentWallieOptions()
options.networkEnvironment = .production  // .production, .staging, or .custom(URL(...))
options.logLevel = .info                  // .none, .error, .warn, .info, .debug
options.defaultPresentation = .modal
 
AgentWallie.configure(apiKey: "pk_your_public_key", options: options)
⚠️

configure() must be called exactly once. Calling it again is a no-op and logs a warning.

User Management

Identify

Call identify when you know the user's identity (e.g., after login):

AgentWallie.shared.identify(userId: "user_123")

Set User Attributes

Provide attributes for audience targeting:

AgentWallie.shared.setUserAttributes([
    "plan": "free",
    "onboarding_complete": true,
    "session_count": 5,
    "age": 28
])

These attributes are available for audience filter evaluation (e.g., user.plan, user.session_count).

Reset

Clear the user identity, attributes, entitlements, and experiment assignments (e.g., on logout):

AgentWallie.shared.reset()

Presenting Paywalls

Register a Placement

The primary API for showing paywalls. When called, the SDK evaluates campaigns, matches audiences, and presents the appropriate paywall:

AgentWallie.shared.register(placement: "feature_gate") {
    // This closure runs if the user already has the required entitlement.
    // No paywall is shown -- proceed with the feature.
    showProFeature()
}

If no campaign matches the placement, the handler closure runs (user proceeds without a paywall).

Programmatic Presentation

Present a specific paywall by ID, bypassing campaign evaluation:

AgentWallie.shared.presentPaywall(id: "pw_abc123")

Get Paywall Without Presenting

Retrieve the paywall schema for a placement without showing it:

AgentWallie.shared.getPaywall(forPlacement: "upgrade") { result in
    switch result {
    case .success(let schema):
        print("Paywall: \(schema.name)")
    case .failure(let error):
        print("No paywall: \(error)")
    }
}

Subscription Status and Entitlements

The SDK needs to know the user's subscription status to evaluate audience entitlement checks:

// Set subscription status
AgentWallie.shared.subscriptionStatus = .active  // .unknown, .active, .inactive, .expired
 
// Set entitlements
AgentWallie.shared.entitlements = ["pro", "premium_content"]

When an audience has an entitlement_check, the SDK skips users who already have that entitlement (no paywall shown, handler runs instead).

Purchases & StoreKit

By default, the SDK handles purchases using StoreKit 2 -- no setup needed. When a user taps a CTA button with action purchase, the SDK resolves the product slot, initiates the StoreKit 2 purchase, validates the receipt server-side, and updates entitlements automatically.

Key capabilities:

  • Automatic product resolution -- paywall slots map to real StoreKit products with live prices
  • Entitlement management -- purchases grant entitlements that gate paywall display
  • Receipt validation -- server-side JWS verification via the receipts API
  • Custom billing support -- plug in RevenueCat or any provider via PurchaseController

For the full integration guide, including custom PurchaseController implementation, expression syntax for prices, entitlement configuration, and troubleshooting, see the dedicated Purchases & StoreKit guide.

Event Tracking

Track custom events for analytics:

AgentWallie.shared.trackEvent(
    name: "workout_completed",
    properties: ["duration": 45, "type": "strength"]
)

Events are batched and sent to POST /v1/events/:public_key periodically.

Delegate Callbacks

Implement AgentWallieDelegate to receive SDK lifecycle events:

class AppCoordinator: AgentWallieDelegate {
    init() {
        AgentWallie.shared.delegate = self
    }
 
    func didPresentPaywall(info: PaywallPresentationInfo) {
        print("Showed paywall: \(info.paywallName ?? "unknown")")
    }
 
    func didDismissPaywall(info: PaywallPresentationInfo) {
        print("Dismissed paywall: \(info.paywallName ?? "unknown")")
    }
 
    func didCompletePurchase(productId: String) {
        print("Purchased: \(productId)")
        // Update subscription status
        AgentWallie.shared.subscriptionStatus = .active
    }
 
    func didRestorePurchases() {
        print("Purchases restored")
    }
 
    func handleCustomAction(name: String) {
        // Handle custom tap actions from paywall buttons
        switch name {
        case "show_onboarding":
            showOnboarding()
        default:
            break
        }
    }
 
    func handleLog(level: LogLevel, message: String) {
        // Optional: forward SDK logs to your logging system
    }
}

PaywallPresentationInfo

PropertyTypeDescription
paywallIdString?Paywall identifier
paywallNameString?Paywall display name
campaignIdString?Campaign that triggered presentation
audienceIdString?Matched audience
experimentIdString?Active experiment
variantIdString?Assigned variant

Custom Views

Register native SwiftUI views to embed inside paywalls. Custom views let you go beyond the built-in component types for brand-specific UI, complex interactions, or third-party integrations.

// Register a simple view
AgentWallie.shared.registerView(name: "PremiumHero") {
    PremiumHeroView()
}
 
// Register a context-aware view
AgentWallie.shared.registerView(name: "PricingCard") { context in
    PricingCardView(data: context.customData)
}

Then reference it in your paywall schema:

{
  "type": "custom_view",
  "id": "hero",
  "props": { "view_name": "PremiumHero" }
}

For the full registration API, context properties, action handling, and best practices, see the Custom Views guide.

The paywall schema also supports rich DSL features including conditionals, animations, and gradients that work with all built-in components without any native code changes.

Deep Links

Handle AgentWallie deep links to present paywalls directly:

// In your SceneDelegate or App
func handleURL(_ url: URL) {
    AgentWallie.shared.handleDeepLink(url)
}

Deep link format: agentwallie://paywall/{paywall_id}

Options Reference

AgentWallieOptions

PropertyTypeDefaultDescription
networkEnvironmentNetworkEnvironment.productionAPI environment
logLevelLogLevel.warnMinimum log level
defaultPresentationPaywallPresentation.modalDefault paywall presentation style

NetworkEnvironment

ValueDescription
.productionProduction API
.stagingStaging environment
.custom(URL(...))Custom API URL (positional argument, for local dev)

LogLevel

ValueDescription
.noneNo logging
.errorErrors only
.warnWarnings and errors
.infoInfo, warnings, and errors
.debugAll logs including debug

Local Development

You can render paywall schemas locally without connecting to the AgentWallie API. This is useful for rapid iteration, snapshot testing, and offline development.

Render a JSON Schema Directly

Decode a bundled JSON file and render it with PaywallView:

import AgentWallieKit
 
// Load from bundled JSON file
guard let url = Bundle.main.url(forResource: "my-paywall", withExtension: "json"),
      let data = try? Data(contentsOf: url),
      let schema = try? JSONDecoder().decode(PaywallSchema.self, from: data) else {
    return
}
 
// Render directly in SwiftUI
PaywallView(
    schema: schema,
    onAction: { action, param in
        print("Action: \(action)")
    },
    onDismiss: {
        dismiss()
    }
)

Use the Dev Server with PaywallDevPreview

For live-reloading previews on the iOS Simulator, pair the paywall-dev serve CLI with the PaywallDevPreview app:

  1. Start the dev server: npx paywall-dev serve my-paywall.json
  2. Build and launch PaywallDevPreview in the Simulator (see Paywall Dev Kit docs)
  3. The preview app polls http://localhost:3456/schema and re-renders on every change

This gives you pixel-perfect native rendering with a fast edit-save-preview loop.

See the full Paywall Dev Kit documentation for CLI commands, mock data setup, and the agent workflow.

Supported Features

All paywall schema features are fully supported in the current iOS SDK:

  • Product picker: "cards", "horizontal", and "vertical" layouts with show_savings_badge, savings_text, show_price, selected_border_color, and glow
  • Expression resolution: {{ products.selected.price }} and other product expressions resolve reactively as the user selects products, with fallback to AWProduct.displayPrice when StoreKit fetch fails
  • Feature list: Per-row styling with background_color, corner_radius, padding, and border_color (accent bar)
  • CTA button: Full styling including glow_color shadow effect
  • Text: letter_spacing, markdown bold (**text**)
  • Close button: Configurable via close_button_style ("icon" or "text")
  • Background gradient: Supported via background_gradient in paywall settings
  • Shimmer animation: Supported via "shimmer" animation type

Full Example

import SwiftUI
import AgentWallieKit
 
@main
struct FitnessApp: App {
    @StateObject private var coordinator = AppCoordinator()
 
    init() {
        var options = AgentWallieOptions()
        options.logLevel = .info
 
        AgentWallie.configure(apiKey: "pk_live_abc123", options: options)
    }
 
    var body: some Scene {
        WindowGroup {
            ContentView()
                .onAppear {
                    AgentWallie.shared.delegate = coordinator
                    AgentWallie.shared.identify(userId: "user_456")
                    AgentWallie.shared.setUserAttributes([
                        "plan": "free",
                        "workouts_completed": 12
                    ])
                }
        }
    }
}
 
struct WorkoutView: View {
    var body: some View {
        Button("Start Advanced Workout") {
            AgentWallie.shared.register(placement: "advanced_workout") {
                // User has pro entitlement -- let them through
                startAdvancedWorkout()
            }
        }
    }
 
    func startAdvancedWorkout() {
        // Navigate to workout
    }
}
 
class AppCoordinator: ObservableObject, AgentWallieDelegate {
    func didPresentPaywall(info: PaywallPresentationInfo) {}
    func didDismissPaywall(info: PaywallPresentationInfo) {}
    func didCompletePurchase(productId: String) {
        AgentWallie.shared.subscriptionStatus = .active
        AgentWallie.shared.entitlements = ["pro"]
    }
    func didRestorePurchases() {}
    func handleCustomAction(name: String) {}
    func handleLog(level: LogLevel, message: String) {}
}