API Reference

Step 1. Add required info from Adapty to web2wave project settings

  • Adapty API keys (for both platforms separately)
  • Access level to grant to the user with subscription
  • Optionally – which User Answers/Properties to pass to Adapty

Step 2. Add your deeplink

  1. Go to Deeplink tab
  2. Click on help icon on the right to the field to launch Helper
  3. Add base deeplink
    1. https://YYYYY.onelink.me/XXXXXX?af_xp=custom&pid=web2wave&deep_link_value=
  4. Add user properties you want to include
    1. Usually just user_id
  5. Final link will be inserted
    1. https://YYYYY.onelink.me/XXXXXX?af_xp=custom&pid=web2wave&deep_link_value=%7B%22user_id%22%3A%22{user_id}%22%7D&user_id={user_id}

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


3. Read deeplink value and send Adapty Profile ID to web2wave

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


Here is an example for AppsFlyer:

  1. Read deeplink_value, extract user_id from JSON
  2. Get Adapty Profile ID
  3. Send it to web2wave API with web2wave's user_id from deeplink
    1. API endpoint: https://web2-tfsv.readme.io/reference/post_user-properties
    2. iOS SDK – https://github.com/web2wave/web2wave_swift
    3. Flutter SDK – https://github.com/web2wave/web2wave_flutter
    4. Kotlin SDK – https://github.com/web2wave/web2wave_kotlin
    5. Java SDK – https://github.com/web2wave/web2wave_java
Add the following to your Package.swift file:

dependencies: [
    .package(url: "https://github.com/web2wave/web2wave_swift.git", from: "1.0.0")
]

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()
    }

    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
        fetchAdaptyProfileID()
    }
    
    private func fetchAdaptyProfileID() {
        Adapty.getProfile { [weak self] profile, error in
            guard let self = self, let adaptyProfileID = profile?.profileId, let userId = self.userId else { return }
            self.sendProfileIDToWeb2Wave(userId: userId, adaptyProfileID: adaptyProfileID)
        }
    }
    
    private func sendProfileIDToWeb2Wave(userId: String, adaptyProfileID: String) async {
        if case .failure(let error) = await Web2Wave.shared.setAdaptyProfileID(appUserID: userId, adaptyProfileID: adaptyProfileID) {
            print("Error sending Adapty profile ID: \(error)")
        }
    }
}
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.appsflyer.AppsFlyerLib
import com.appsflyer.deeplink.DeepLinkListener
import com.appsflyer.deeplink.DeepLinkResult
import com.adapty.Adapty
import com.adapty.errors.AdaptyError
import com.adapty.models.Profile
import okhttp3.*
import org.json.JSONObject
import java.io.IOException

class MainActivity : AppCompatActivity() {

    private val client = OkHttpClient()
    private var userId: String? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initialize Adapty
        Adapty.activate(this, "your_adapty_public_sdk_key") // Replace with your Adapty SDK key

        // Start AppsFlyer SDK
        AppsFlyerLib.getInstance().start(this)
        AppsFlyerLib.getInstance().subscribeForDeepLink(object : DeepLinkListener {
            override fun onDeepLinking(result: DeepLinkResult) {
                when (result.status) {
                    DeepLinkResult.Status.FOUND -> {
                        val deepLink = result.deepLink
                        deepLink?.let { handleDeepLink(it) }
                    }
                    DeepLinkResult.Status.NOT_FOUND -> {
                        println("Deep link not found")
                    }
                    DeepLinkResult.Status.ERROR -> {
                        println("Error resolving deep link: ${result.error?.message}")
                    }
                }
            }
        })
    }

    private fun handleDeepLink(deepLink: DeepLinkResult.DeepLink) {
        val pid = deepLink.clickEvent["pid"] as? String
        if (pid == "web2wave") {
            val deepLinkValue = deepLink.clickEvent["deep_link_value"] as? String
            deepLinkValue?.let {
                // Parse deep_link_value to extract user_id
                val jsonObject = JSONObject(deepLinkValue)
                userId = jsonObject.optString("user_id", "")
                if (!userId.isNullOrEmpty()) {
                    println("User ID from deep link: $userId")
                    // Fetch Adapty Profile ID
                    fetchAdaptyProfileID()
                } else {
                    println("Failed to extract user_id from deep_link_value")
                }
            }
        }
    }

    private fun fetchAdaptyProfileID() {
        Adapty.getProfile(object : (Profile?, AdaptyError?) -> Unit {
            override fun invoke(profile: Profile?, error: AdaptyError?) {
                if (error != null) {
                    println("Failed to fetch Adapty profile: ${error.message}")
                    return
                }

                profile?.let {
                    val adaptyProfileID = it.profileId
                    println("Adapty Profile ID: $adaptyProfileID")

                    // Send Adapty Profile ID to external API
                    userId?.let { userId ->
                        sendAdaptyProfileIDToAPI(userId, adaptyProfileID)
                    }
                }
            }
        })
    }

    private fun sendAdaptyProfileIDToAPI(userId: String, adaptyProfileID: String) {
        val url = "https://quiz.yourdomain.com/api/user/properties"
        val jsonBody = JSONObject()
        jsonBody.put("property", "adapty_profile_id")
        jsonBody.put("value", adaptyProfileID)
        jsonBody.put("user", userId)

        val body = RequestBody.create(MediaType.get("application/json; charset=utf-8"), jsonBody.toString())
        val request = Request.Builder()
            .url(url)
            .addHeader("api-key", "4c54fde2-3e09-XXXXXXXXX") // Replace with your actual API key
            .post(body)
            .build()

        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                println("Failed to send data to external API: ${e.message}")
            }

            override fun onResponse(call: Call, response: Response) {
                if (response.isSuccessful) {
                    println("Successfully sent data to web2wave API")
                } else {
                    println("Error sending data to web2wave API: ${response.message}")
                }
            }
        })
    }
}
import 'package:flutter/material.dart';
import 'package:appsflyer_sdk/appsflyer_sdk.dart';
import 'package:adapty_flutter/adapty_flutter.dart';
import 'package:web2wave/web2wave.dart';
import 'dart:convert';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String? userId;
  late AppsFlyerSdk _appsflyerSdk;

  @override
  void initState() {
    super.initState();
    Web2Wave.shared.initialize(apiKey: 'your-api-key');
    Adapty().activatePublicSdkKey('your_adapty_public_sdk_key');

    _appsflyerSdk = AppsFlyerSdk(
      AppsFlyerOptions(afDevKey: 'your_apps_flyer_dev_key', appId: 'your_app_id', showDebug: true),
    )..onDeepLink((deepLinkData) => _handleDeepLink(deepLinkData))
     ..startSDK();
  }

  void _handleDeepLink(Map<String, dynamic> deepLink) async {
    final deepLinkValue = deepLink['deep_link_value'];
    if (deepLinkValue is String) {
      try {
        final extractedUserId = jsonDecode(deepLinkValue)['user_id'];
        if (extractedUserId is String) {
          setState(() => userId = extractedUserId);
          final profile = await Adapty().getProfile();
          if (profile?.profileId != null) {
            await Web2Wave.shared.setAdaptyProfileID(userId: extractedUserId, adaptyProfileId: profile!.profileId!);
          }
        }
      } catch (e) {
        print('Error parsing deep link JSON: $e');
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Web2Wave Integration')),
      body: Center(
        child: Text(userId != null ? 'User ID: $userId' : 'Waiting for deep link...'),
      ),
    );
  }
}

Restore purchases in 1-2 seconds: https://adapty.io/docs/restore-purchase

Adapty.restorePurchases { [weak self] result in
    switch result {
        case let .success(profile):
            if info.profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive ?? false {
                // successful access restore
            }
        case let .failure(error):
            // handle the error
    }
}


Adapty.restorePurchases { result ->
    when (result) {
        is AdaptyResult.Success -> {
            val profile = result.value
                      
            if (profile.accessLevels["YOUR_ACCESS_LEVEL"]?.isActive == true) {
            // successful access restore
            }
        }
        is AdaptyResult.Error -> {
            val error = result.error
            // handle the error
        }
    }
}

4. The entitlement will be granted to the user on Adapty

All subscription changes – pauses, cancellations, will be reflected on the user later.


5. Purchases Restore after app reinstall

Adapty creates a new Profile every time the app is reinstalled. This means that whenever a user deletes and reinstalls the app, they will receive a new Adapty Profile ID, causing their subscription to reset.

https://adapty.io/docs/profiles-crm#profile-record-creation

🔧 How to Fix This

To prevent subscription resets and ensure continuity, you need to assign a persistentcustomerUserId when the app launches. This ensures that Adapty recognizes returning users even after they reinstall the app.

✅ Steps to Implement:

  1. Generate a Persistent User Identifier

    • Use an external identifier such as an email, phone number, or authentication ID from your backend.
    • Alternatively, generate a unique ID and store it securely to persist across reinstalls.
  2. Store the Identifier on Device

    • iOS (Swift): Use Keychain instead of UserDefaults, as UserDefaults is cleared upon uninstallation.
    • Android (Kotlin): Use EncryptedSharedPreferences or Google Account Backup.
    • Flutter: Use secure_storage or an equivalent method.
    • React Native: Use AsyncStorage or react-native-keychain.
  3. PasscustomerUserId to Adapty on App Launch

    Adapty.identify(customerUserId: storedUserId) { error in
        if let error = error {
            print("Adapty identification error: \(error.localizedDescription)")
        }
    }

This ensures that the same Adapty Profile ID is used, even if the app is reinstalled.

Sync with Your Backend (Optional) If your app has a backend, store the user’s Adapty profile ID and map it to their account. This allows restoring the subscription from the backend if needed.