Direct integration in mobile app with web2wave API

🤖

Integrate with an AI coding agent — Cursor, Claude Code, Codex, Windsurf and others.

Open your mobile app project in the agent, paste the prompt below, and answer its questions. The agent reads the web2wave mobile integration playbook, detects your subscription provider and attribution tool, and does the integration step by step.

You are integrating web2wave into this mobile app.

1. Read the web2wave mobile integration playbook and follow it end to end:
   https://raw.githubusercontent.com/web2wave/integration-skills/main/docs/web2wave-mobile-integration-playbook.md
   (Docs index for agents: https://docs.web2wave.com/llms.txt)

2. First inspect this project: the platform (iOS, Android, Flutter, React Native, Unity),
   the subscription provider (RevenueCat, Adapty, Qonversion, Apphud, Superwall) and the
   attribution tool (AppsFlyer, Adjust, Branch). Show me what you found and ask me to confirm.

3. Ask me every question the playbook lists, in its order. Do not guess settings or secrets.
   Never hardcode API keys: ask me for my web2wave API key and keep it in config / env.

4. Implement only the branch we agreed on. Use the web2wave SDK for this platform and take
   code samples from the documentation pages the playbook links to.

5. At the end, list the web2wave project settings I still have to fill in, and run or
   describe the acceptance tests from the playbook.

Where to paste it: the chat or agent panel in Cursor and Windsurf, the terminal session in Claude Code and Codex. You can also save the playbook as a project skill or instructions file and keep the prompt short.

Step 1. Get API key from project settings

Step 2. Add your deeplink in project settings

📘

Using an MMP? Configure the deeplink below. Not using AppsFlyer, Adjust, or Branch? Skip to Step 3 and use web2wave deferred deeplinks instead — no deeplink setup required.

  1. Go to Deeplinks & Billing tab
  2. Click on help icon on the right to the field to launch Helper
  3. Add base deeplink
  4. Add user properties you want to include
    • Usually just user_id
  5. Final link will be inserted

After the purchase of a subscription on web, user will get this deeplink to install the app.

Step 3. Resolve user_id and check subscription status

With an MMP — read user_id from the deeplink (AppsFlyer example below).

Without an MMP — call identify() on first launch. See web2wave deferred deeplinks.

AppsFlyer example

  1. Read user_id from deeplink value using AppsFlyer

  2. Store user_id locally on the device

  3. Request web2wave API

  4. If "subscriptions" array contains subscription with (subscription.status = active || subscription.status = trialing)

    • then don't show paywalls and give the user paid content
  5. Update subscription status on every app launch, if no result returned - use previous value

  6. In Subscription management section in the app show the user button with "manage_link" URL

  7. Use SDK to simplify requests

    iOS SDK – https://github.com/web2wave/web2wave_swift

    Flutter SDK – https://github.com/web2wave/web2wave_flutter

    Kotlin SDK – https://github.com/web2wave/web2wave_kotlin

    Java SDK – https://github.com/web2wave/web2wave_java

Sample code:

import UIKit
import AppsFlyerLib
import Adapty
import Web2Wave

class ViewController: UIViewController, DeepLinkDelegate {
    var userId: String?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        Adapty.activate("your_adapty_public_sdk_key")
        AppsFlyerLib.shared().deepLinkDelegate = self
        AppsFlyerLib.shared().start()
        
        if let userId = UserDefaults.standard.string(forKey: "userId") {
            fetchSubscriptionStatus(userId: userId)
        }
    }
    
    func didResolveDeepLink(_ result: DeepLinkResult) {
        guard case .found = result.status, let deepLink = result.deepLink else { return }
        handleDeepLink(deepLink: deepLink)
    }
    
    private func handleDeepLink(deepLink: DeepLink) {
        guard let deepLinkValue = deepLink["deep_link_value"] as? String,
              let data = deepLinkValue.data(using: .utf8),
              let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let extractedUserId = json["user_id"] as? String else { return }
        
        self.userId = extractedUserId
        UserDefaults.standard.set(extractedUserId, forKey: "userId")
        fetchSubscriptionStatus(userId: extractedUserId)
    }
    
    private func fetchSubscriptionStatus(userId: String) async {
        // Fetch user subscriptions
        let isActive = await Web2Wave.shared.hasActiveSubscription(userID: userId)
        DispatchQueue.main.async {
            isActive ? self.providePaidContent() : self.showPaywall()
        }
    }
    
    private func providePaidContent() {
        print("User has an active subscription. Providing access to paid content.")
    }
    
    private func showPaywall() {
        print("No active subscription. Displaying paywall.")
    }
    
    // Example usage of Web2Wave library methods
    private func web2WaveExamples() async {
        guard let userId = userId else { return }
        
        // Fetch all user properties
        let properties = await Web2Wave.shared.fetchUserProperties(userID: userId)
        print("User properties: \(String(describing: properties))")
        
        // Update a user property
        let updateResult = await Web2Wave.shared.updateUserProperty(
            userID: userId,
            property: "theme",
            value: "dark"
        )
        if case .failure(let error) = updateResult {
            print("Failed to update user property: \(error)")
        }
        
        // Set Adapty Profile ID
        let adaptyResult = await Web2Wave.shared.setAdaptyProfileID(
            appUserID: userId,
            adaptyProfileID: "example_adapty_id"
        )
        if case .failure(let error) = adaptyResult {
            print("Failed to save Adapty profile ID: \(error)")
        }
    }
}
import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.appsflyer.AppsFlyerLib
import com.appsflyer.deeplink.DeepLink
import com.appsflyer.deeplink.DeepLinkListener
import kotlinx.coroutines.*
import web2wave.Web2Wave
import org.json.JSONObject

class MainActivity : AppCompatActivity() {

    private val scope = CoroutineScope(Dispatchers.IO)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Web2Wave.initWith("YOUR_API_KEY_TO_WEB2WAVE")
        AppsFlyerLib.getInstance().apply {
            init("yourAppsFlyerDevKey", null, this@MainActivity)
            start(this@MainActivity)
            registerConversionListener(this@MainActivity, object : DeepLinkListener {
                override fun onDeepLinking(deepLink: DeepLink?) {
                    deepLink?.deepLinkValue?.let { handleDeepLink(it) }
                }
                override fun onAttributionFailure(error: String?) {
                    println("Failed deep link: $error")
                }
            })
        }
        getSharedPreferences("prefs", Context.MODE_PRIVATE)
            .getString("userId", null)?.let { fetchSubscriptionStatus(it) }
    }

    private fun handleDeepLink(deepLinkValue: String) {
        runCatching {
            JSONObject(deepLinkValue).getString("user_id").also {
                getSharedPreferences("prefs", Context.MODE_PRIVATE).edit().putString("userId", it).apply()
                fetchSubscriptionStatus(it)
            }
        }.onFailure { println("Failed to parse deep link: ${it.message}") }
    }

    private fun fetchSubscriptionStatus(userId: String) {
        scope.launch {
            val isActive = runCatching { Web2Wave.hasActiveSubscription(userId) }.getOrDefault(false)
            runOnUiThread { if (isActive) providePaidContent() else showPaywall() }
        }
    }

    private fun providePaidContent() = println("Access granted to paid content.")
    private fun showPaywall() = println("Displaying paywall.")
}
import 'package:flutter/material.dart';
import 'package:web2wave/web2wave.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  Web2Wave.shared.initialize(apiKey: 'your-api-key');
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  String? userId;

  @override
  void initState() {
    super.initState();
    _initializeUser();
  }

  Future<void> _initializeUser() async {
    final prefs = await SharedPreferences.getInstance();
    final storedUserId = prefs.getString('userId');
    if (storedUserId != null) {
      setState(() {
        userId = storedUserId;
      });
      _fetchSubscriptionStatus(storedUserId);
    }
  }

  Future<void> _handleDeepLink(String deepLinkValue) async {
    try {
      final Map<String, dynamic> json = deepLinkValue as Map<String, dynamic>;
      final extractedUserId = json['user_id'] as String?;
      if (extractedUserId != null) {
        final prefs = await SharedPreferences.getInstance();
        await prefs.setString('userId', extractedUserId);
        setState(() {
          userId = extractedUserId;
        });
        _fetchSubscriptionStatus(extractedUserId);
      }
    } catch (e) {
      print('Failed to parse deep link: $e');
    }
  }

  Future<void> _fetchSubscriptionStatus(String userId) async {
    final isActive = await Web2Wave.shared.hasActiveSubscription(web2waveUserId: userId);
    if (isActive) {
      _providePaidContent();
    } else {
      _showPaywall();
    }
  }

  void _providePaidContent() {
    print("User has an active subscription. Providing access to paid content.");
  }

  void _showPaywall() {
    print("No active subscription. Displaying paywall.");
  }

  Future<void> _web2WaveExamples() async {
    if (userId == null) return;

    // Fetch user properties
    final properties = await Web2Wave.shared.fetchUserProperties(web2waveUserId: userId!);
    print("User properties: ${properties.toString()}");

    // Update a user property
    final updateResult = await Web2Wave.shared.updateUserProperty(
      web2waveUserId: userId!,
      property: "theme",
      value: "dark",
    );
    if (!updateResult.isSuccess) {
      print("Failed to update user property: ${updateResult.errorMessage}");
    }

    // Set Adapty Profile ID
    final adaptyResult = await Web2Wave.shared.setAdaptyProfileID(
      web2waveUserId: userId!,
      adaptyProfileId: "example_adapty_id",
    );
    if (!adaptyResult.isSuccess) {
      print("Failed to save Adapty profile ID: ${adaptyResult.errorMessage}");
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Web2Wave Example")),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(userId != null ? "User ID: $userId" : "No User ID"),
            ElevatedButton(
              onPressed: _web2WaveExamples,
              child: Text("Test Web2Wave"),
            ),
          ],
        ),
      ),
    );
  }
}