Overview:
This document provides a comprehensive guide on how to implement subscription events using the Singular SDK. It covers in-app subscription event tracking across various platforms, server-to-server (S2S) event integration, and third-party integrations like RevenueCat and Adapty.
Singular enables you to track your subscriptions and renewals within your app, providing insights into user behavior and revenue generation. Singular's subscription attribution offers robust methods for tracking subscription events, ensuring accurate and real-time data collection.
Considerations | SDK | S2S | S2S 3P Integration |
Implementation | Send subscription events to Singular via the custom Revenue methods in the SDK | Send subscription events managed in your backend to Singular's REST API (direct S2S implementation) with required mobile device properties | S2S integrations are managed by third-party partners, and configuration is required in their respective platforms |
Reliance on App Activity | The app must be launched for the Singular SDK to send the subscription event | Subscription events can be sent in real time, server-to-server | Subscription events are sent in real time from third-party partners |
Event Timing | Since sending the event relies on app launch (and initialization of the Singular SDK), the event sent to Singular can have a different event time than the actual subscription time | Subscription events can be sent in real time, as they are processed in your backend | Subscription events can be sent in near real time, after being processed by the 3P vendor |
Note:
If you choose to send subscription events via one of the S2S integrations, most customers should still integrate the Singular SDK to manage sessions and non-subscription events.Implement Subscription State Management in App
By integrating Google's Play Billing Library and Apple's StoreKit, you can query subscription information and manage the subscription state directly on the device. This setup allows for integration with one of Singular's implementation methods. Below are sample code snippets demonstrating simple implementations of the Billing Library and StoreKit to serve as practical examples. You should modify these examples in Production to fit your App's code and subscription model.
Android: Integrating Google Play Billing Library
1. Add the Billing Library to Your Project
Add the Google Play Billing Library configuration in your build.gradle
2. Set Up In-App Products and Subscriptions
Configure your in-app products and subscription items in the Google Play Console
3. Query Subscription Information
Use the BillingClient to query purchase history and subscription status within your app.
val billingClient = BillingClient.newBuilder(context)
.setListener(purchaseUpdateListener)
.enablePendingPurchases()
.build()
billingClient.startConnection(object : BillingClientStateListener {
override fun onBillingSetupFinished(billingResult: BillingResult) {
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
// The billing client is ready, you can now query subscription information asynchronously
billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS) {result, subscriptionList ->
if (result.responseCode == BillingClient.BillingResponseCode.OK && subscriptionList != null) {
// Handle the retrieved subscription list
subscriptionList.forEach { purchase ->
// Process each subscription purchase
}
} else {
// Handle error in queryPurchasesAsync
}
}
} else {
// Handle error on setup finished
}
}
override fun onBillingServiceDisconnected() {
// Retry connection after a delay if the service gets disconnected
Handler(Looper.getMainLooper()).postDelayed({
billingClient.startConnection(this)
}, 3000)
}
})
// Remember to properly disconnect the billing client in your activity/fragment lifecycle
override fun onDestroy() {
billingClient.endConnection()
super.onDestroy()
}
BillingClient billingClient = BillingClient.newBuilder(context)
.setListener(purchaseUpdateListener)
.enablePendingPurchases()
.build();
BillingClientStateListener billingClientStateListener = new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
// The billing client is ready, you can now query subscription information asynchronously
billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS, (result, purchasesList) -> {
if (result.getResponseCode() == BillingClient.BillingResponseCode.OK && purchasesList != null) {
// Handle the retrieved subscription list
for (Purchase purchase : purchasesList) {
// Process each subscription purchase
}
} else {
// Handle error in queryPurchasesAsync
}
});
} else {
// Handle error on setup finished
}
}
@Override
public void onBillingServiceDisconnected() {
// Retry connection after a delay if the service gets disconnected
new Handler(Looper.getMainLooper()).postDelayed(() -> billingClient.startConnection(billingClientStateListener), 3000);
}
};
billingClient.startConnection(billingClientStateListener);
@Override
protected void onDestroy() {
super.onDestroy();
if (billingClient != null) {
billingClient.endConnection();
}
}
iOS: Integrating StoreKit
1. Implement StoreKit
Integrate the StoreKit Framework into your iOS app.
2. Set Up In-App Products and Subscriptions
Set up in-app products and subscription items in App Store Connect
3. Query Subscription Information
Use SKPaymentQueue and SKReceiptRefreshRequest to get the subscription status and receipt information.
import StoreKit
class YourViewController: UIViewController, SKPaymentTransactionObserver {
override func viewDidLoad() {
super.viewDidLoad()
// Add the observer for payment transactions
SKPaymentQueue.default().add(self)
// Refresh the receipt to get the latest subscription information
refreshReceipt()
}
func refreshReceipt() {
let request = SKReceiptRefreshRequest()
request.delegate = self
request.start()
}
// Implement SKPaymentTransactionObserver methods
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction in transactions {
switch transaction.transactionState {
case .purchased, .restored:
// Handle the purchase or restore
// You can check the transaction.transactionReceipt for receipt information
break
case .failed:
// Handle the failed transaction
break
case .deferred:
// Handle the deferred transaction
break
case .purchasing:
// Transaction is in progress
break
@unknown default:
break
}
}
}
// Implement SKRequestDelegate method
func requestDidFinish(_ request: SKRequest) {
// Receipt refresh request completed
// Now you can use the updated receipt to check subscription status
}
func request(_ request: SKRequest, didFailWithError error: Error) {
// Handle the error during the receipt refresh request
}
}
Sending Subscription Events from the Singular SDK
When the subscription state changes in the implementations above, a new custom Revenue event should be sent to Singular. DO NOT use the IAP functions and DO NOT send a receipt value to Singular. Follow the respective implementation guide for the Singular SDK you've implemented in your app to send a customRevenue() event without the receipt.
-
iOS SDK: use this method to send the "subscription" event name:
(void)customRevenue:(NSString*)eventname currency:(NSString *)currency amount:(double)amount withAttributes:(NSDictionary*)attributes;
-
Android SDK use this method to send the "subscription" event name:
Singular.customRevenue(String eventName, String currency, double amount, Map<String, Object> attributes)
-
React Native SDK: use this method to send the "subscription" event name:
static customRevenueWithArgs(eventName: string, currency: string, amount: number, args: SerializableObject): void;
-
Unity SDK: use this method to send the "subscription" event name:
SingularSDK.CustomRevenue(string eventName, string currency, double amount, Dictionary<string, object> attributes)
-
Flutter SDK: use this method to send the "subscription" event name:
Singular.customRevenueWithAttributes(String eventName, String currency, double amount, Map attributes)
-
Cordova SDK: use this method to send the "subscription" event name:
SingularCordovaSdk.customRevenueWithArgs(String eventName, String currency, double amount, JSONObject args)
Sending Subscription Events S2S
If you prefer to send subscription events in real time, and manage subscription events in your back end, you can send events to Singular's REST API.
Use the Event Endpoint to send subscription events to Singular, and be sure to include the Revenue parameters.
- If you have implemented the Singular SDK:
- You should not send subscription events from the SDK with this option
- You do not need to integrate with the launch (Session) S2S endpoint
Sending Subscription Events via 3P Integration
If you are using a 3rd party service to manage subscriptions, those vendors capture subscription data via their own integration methods. They then send subscription events to Singular's REST API, which is already integrated in their systems. These integrations require some configurations in the partner platforms - see their respective guides below:
Subscription Events
Several different subscription events can be sent to Singular. Note that Singular's standard event names are provided where applicable below (see relevant SDK documentation), but custom event names can also be used if desired.
Subscription State | Event | SDK Method | S2S Integration |
Subscription | sng_subscribe |
|
|
Trial Start | sng_start_trial |
|
|
Trial End | sng_end_trial (not officially a standard event yet, so this will be sent as a custom event) |
|
|
Refund | subscription_refunded |
|
|
Cancellation | subscription_cancelled |
|
|
Subscription Renewal | subscription_renewed |
|
|
Note:
If you choose to send subscription events via customRevenue method, be sure to only send events where isRestored is false.Notes on SKAN Measurement
Singular can measure subscription event data via all integration methods noted above. Note that depending on the SKAN version, event measurement may be limited depending on the SKAN postback measurement window.
If you plan to implement the Singular SDK for lifecycle events, and use one of the S2S integration methods above to send subscription events, reach out to your CSM to enable Hybrid SKAN.