Step 1. Add required info from RevenueCat to web2wave project settings
- RevenueCat Project ID
- API keys
- Entitlement name
Step 2. Add your deeplink
- Go to Deeplink tab
- Click on help icon on the right to the field to launch Helper
- Add base deeplink
- Add user properties you want to include
- Usually just user_id
- Final link will be inserted

After the purchase of a subscription on web, user will get this deeplink to install the app.
3. Read deeplink value and send RevenueCat App User ID to web2wave
Here is an example for AppsFlyer:
- Read deeplink_value, extract user_id from JSON
- Get RevenueCat App User ID
- Send it to web2wave API with web2wave's user_id from deeplink
- API endpoint: https://web2-tfsv.readme.io/reference/post_user-properties
- 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
import UIKit
import AppsFlyerLib
import Purchases
import Web2Wave
class ViewController: UIViewController, DeepLinkDelegate {
var userId: String?
override func viewDidLoad() {
super.viewDidLoad()
Purchases.configure(withAPIKey: "your_revenuecat_public_api_key") // Initialize RevenueCat
AppsFlyerLib.shared().deepLinkDelegate = self
AppsFlyerLib.shared().start() // Set up AppsFlyer SDK
Web2Wave.shared.apiKey = "your-api-key" // Configure Web2Wave SDK
}
func didResolveDeepLink(_ result: DeepLinkResult) {
if case .found = result.status, let deepLink = result.deepLink {
handleDeepLink(deepLink)
} else {
print("Deep link not found or failed: \(String(describing: result.error))")
}
}
private func handleDeepLink(_ deepLink: DeepLink) {
guard let deepLinkValue = deepLink["deep_link_value"] as? String,
let userData = deepLinkValue.data(using: .utf8),
let userDict = try? JSONSerialization.jsonObject(with: userData) as? [String: Any],
let extractedUserId = userDict["user_id"] as? String else {
print("Failed to parse deep_link_value")
return
}
userId = extractedUserId
print("User ID from deep link: \(extractedUserId)")
fetchRevenueCatAppUserID()
}
private func fetchRevenueCatAppUserID() {
Purchases.shared.getCustomerInfo { customerInfo, error in
guard let appUserID = customerInfo?.appUserID else {
print("Failed to fetch RevenueCat user info: \(error?.localizedDescription ?? "Unknown error")")
return
}
print("RevenueCat App User ID: \(appUserID)")
if let userId = self.userId { Task { await self.sendAppUserIDToWeb2Wave(userId, appUserID) } }
}
}
private func sendAppUserIDToWeb2Wave(_ userId: String, _ appUserID: String) async {
switch await Web2Wave.shared.setRevenuecatProfileID(web2waveUserId: userId, revenueCatProfileID: appUserID) {
case .success: print("Successfully sent RevenueCat ID to Web2Wave API")
case .failure(let error): print("Error sending data to Web2Wave API: \(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 com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.PurchasesConfiguration
import com.revenuecat.purchases.CustomerInfo
import com.revenuecat.purchases.interfaces.ReceiveCustomerInfoCallback
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")
// Initialize RevenueCat
Purchases.configure(PurchasesConfiguration.Builder(this, "your_revenuecat_public_api_key").build())
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)
fetchRevenueCatProfileID(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 fetchRevenueCatProfileID(userId: String) {
Purchases.sharedInstance.getCustomerInfo(object : ReceiveCustomerInfoCallback {
override fun onReceived(customerInfo: CustomerInfo) {
val revenueCatProfileID = customerInfo.appUserID
scope.launch {
Web2Wave.setRevenuecatProfileID(userId, revenueCatProfileID)
}
}
override fun onError(error: com.revenuecat.purchases.PurchasesError) {
println("Failed to fetch RevenueCat user info: ${error.message}")
}
})
}
private fun providePaidContent() = println("Access granted to paid content.")
private fun showPaywall() = println("Displaying paywall.")
}
import 'package:flutter/material.dart';
import 'package:appsflyer_sdk/appsflyer_sdk.dart';
import 'package:purchases_flutter/purchases_flutter.dart';
import 'package:web2wave/web2wave.dart';
class DeepLinkHandler extends StatefulWidget {
@override
_DeepLinkHandlerState createState() => _DeepLinkHandlerState();
}
class _DeepLinkHandlerState extends State<DeepLinkHandler> {
String? userId;
late AppsflyerSdk _appsflyerSdk;
@override
void initState() {
super.initState();
_initializeSDKs();
}
void _initializeSDKs() async {
await Purchases.setup("your_revenuecat_public_api_key"); // Initialize RevenueCat
Web2Wave.shared.initialize(apiKey: 'your-api-key'); // Configure Web2Wave SDK
_appsflyerSdk = AppsflyerSdk(
AppsFlyerOptions(afDevKey: 'your_dev_key', appId: 'your_app_id')
);
_appsflyerSdk.onDeepLink((deepLink) => _handleDeepLink(deepLink));
_appsflyerSdk.startSDK();
}
void _handleDeepLink(Map<dynamic, dynamic> deepLink) {
final deepLinkValue = deepLink['deep_link_value'];
if (deepLinkValue != null) {
try {
final userDict = Map<String, dynamic>.from(deepLinkValue);
userId = userDict['user_id'];
if (userId != null) {
print("User ID from deep link: $userId");
_fetchRevenueCatAppUserID();
}
} catch (e) {
print("Failed to parse deep_link_value: $e");
}
}
}
void _fetchRevenueCatAppUserID() async {
try {
CustomerInfo customerInfo = await Purchases.getCustomerInfo();
String appUserID = customerInfo.originalAppUserId;
print("RevenueCat App User ID: $appUserID");
if (userId != null) _sendAppUserIDToWeb2Wave(userId!, appUserID);
} catch (e) {
print("Failed to fetch RevenueCat user info: $e");
}
}
Future<void> _sendAppUserIDToWeb2Wave(String userId, String appUserID) async {
final result = await Web2Wave.shared.setRevenuecatProfileID(
web2waveUserId: userId, revenuecatProfileId: appUserID
);
if (result.isSuccess) {
print("Successfully sent RevenueCat ID to Web2Wave API");
} else {
print("Error sending data to Web2Wave API: ${result.errorMessage}");
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Deep Link Handler')),
body: Center(child: Text('Waiting for deep links...')),
);
}
}
Restore purchases in 1-2 seconds: https://www.revenuecat.com/docs/getting-started/restoring-purchases
Purchases.shared.restorePurchases { customerInfo, error in
// ... check customerInfo to see if entitlement is now active
}
Purchases.sharedInstance.restorePurchasesWith() { customerInfo ->
//... check customerInfo to see if entitlement is now active
}
4. The entitlement will be granted to the user on RevenueCat
All subscription changes – pauses, cancellations, will be reflected on the user later.
