サブスクリプションイベントトラッキング実装ガイド

サブスクリプションイベントトラッキング実装ガイド

包括的なサブスクリプショントラッキングを実装して、Singular の SDK、 サーバー間 (server-to-server) 連携、またはサードパーティパートナーを使用し、 すべてのプラットフォームにわたってサブスクリプション収益、ユーザーのリテンション、 キャンペーンパフォーマンスを測定します。

Singular では、新規サブスクリプション、更新、トライアル、キャンセル、返金を含む サブスクリプションイベントを、ユーザーを獲得したマーケティングキャンペーンに正確に アトリビューションできます。これにより、サブスクリプションのライフサイクルと収益創出を 完全に可視化できます。


実装オプション

アプリのアーキテクチャとビジネス要件に最も適した実装方法を選択してください。

検討事項 SDK 連携 サーバー間連携 (S2S) サードパーティ連携
実装 Singular SDK のカスタム収益メソッドを介してサブスクリプションイベントを送信します バックエンドから、必要なモバイルデバイスプロパティを付けて Singular の REST API にイベントを送信します RevenueCat、Adapty、Superwall、Xsolla、その他のサブスクリプションプラットフォームとの 連携を構成します
アプリアクティビティへの依存 SDK がイベントを送信するにはアプリを起動する必要があります アプリの状態に依存せず、イベントはリアルタイムで送信されます イベントはパートナープラットフォームからリアルタイムで送信されます
イベントのタイミング アプリの起動に応じて、イベント時刻が実際のサブスクリプション時刻と 異なる場合があります バックエンドで処理されるとおりにリアルタイムでイベントをトラッキングします パートナーによる処理後にほぼリアルタイムでトラッキングします
複雑さ シンプルなクライアント側の実装 安全なバックエンドインフラが必要です パートナーアカウントと構成が必要です

重要: サブスクリプションイベントに S2S またはサードパーティ連携を使用する ほとんどのお客様は、完全なアトリビューショントラッキングのために、セッションおよび サブスクリプション以外のイベントを管理する目的で Singular SDK も引き続き連携させる 必要があります。


Android: Google Play Billing 連携

Google Play Billing Library 8.0.0 を連携させてサブスクリプション情報を照会し、 Singular SDK 連携のためにデバイス上で直接サブスクリプションの状態を管理します。

前提条件

Google Play Console を構成する

アプリで課金を実装する前に、 Google Play Console でアプリ内商品とサブスクリプションアイテムを設定します。

  1. サブスクリプション商品を作成する: Play Console でサブスクリプションの ティア、課金期間、価格を定義します
  2. オファーを構成する: ベースプラン、オファートークン、プロモーション価格を 設定します
  3. テスト設定: テストアカウントを作成し、商品の利用可能性を確認します

Billing Library の依存関係を追加する

build.gradle を更新する

コルーチンをサポートする Kotlin 拡張機能とともに、Google Play Billing Library 8.0.0 を アプリの依存関係に追加します。

Kotlin DSL
dependencies {
    val billingVersion = "8.0.0"

    // Google Play Billing Library
    implementation("com.android.billingclient:billing:$billingVersion")

    // Kotlin extensions and coroutines support
    implementation("com.android.billingclient:billing-ktx:$billingVersion")
}

バージョン 8.0.0 の機能: このバージョンでは、 enableAutoServiceReconnection() による自動サービス再接続、改善された購入照会 API、 強化された保留中トランザクション処理が導入されています。


BillingClient を初期化する

Billing Manager を作成する

すべてのサブスクリプション操作を処理し、イベントトラッキングのために Singular SDK と 連携させる billing manager クラスを設定します。

Kotlin
class SubscriptionManager(private val context: Context) {

    private val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
        when (billingResult.responseCode) {
            BillingResponseCode.OK -> {
                purchases?.forEach { purchase ->
                    handlePurchaseUpdate(purchase)
                }
            }
            BillingResponseCode.USER_CANCELED -> {
                Log.d(TAG, "User canceled the purchase")
            }
            else -> {
                Log.e(TAG, "Purchase error: ${billingResult.debugMessage}")
            }
        }
    }

    private val billingClient = BillingClient.newBuilder(context)
        .setListener(purchasesUpdatedListener)
        .enablePendingPurchases() // Required for all purchases
        .enableAutoServiceReconnection() // NEW in 8.0.0 - handles reconnection automatically
        .build()

    fun initialize() {
        billingClient.startConnection(object : BillingClientStateListener {
            override fun onBillingSetupFinished(billingResult: BillingResult) {
                if (billingResult.responseCode == BillingResponseCode.OK) {
                    Log.d(TAG, "Billing client ready")
                    // Query existing subscriptions
                    queryExistingSubscriptions()
                } else {
                    Log.e(TAG, "Billing setup failed: ${billingResult.debugMessage}")
                }
            }

            override fun onBillingServiceDisconnected() {
                // With enableAutoServiceReconnection(), SDK handles reconnection
                // Manual retry is no longer required
                Log.d(TAG, "Billing service disconnected - auto-reconnection enabled")
            }
        })
    }

    companion object {
        private const val TAG = "SubscriptionManager"
    }
}

主な機能:

  • 自動再接続: バージョン 8.0.0 の enableAutoServiceReconnection() は、必要に応じて接続を自動的に再確立します
  • 購入リスナー: 更新や保留中トランザクションを含む、すべての購入更新の コールバックを受信します
  • エラー処理: ユーザーによるキャンセルや課金エラーに対する包括的な処理

サブスクリプション情報を照会する

有効なサブスクリプションを取得する

更新やアプリ外で購入されたサブスクリプションを処理するために、 queryPurchasesAsync() を使用して既存のサブスクリプションを照会します。

Kotlin
private suspend fun queryExistingSubscriptions() {
    val params = QueryPurchasesParams.newBuilder()
        .setProductType(BillingClient.ProductType.SUBS)
        .build()

    withContext(Dispatchers.IO) {
        val result = billingClient.queryPurchasesAsync(params)

        if (result.billingResult.responseCode == BillingResponseCode.OK) {
            result.purchasesList.forEach { purchase ->
                when (purchase.purchaseState) {
                    Purchase.PurchaseState.PURCHASED -> {
                        // Active subscription - process it
                        handleSubscriptionPurchase(purchase)
                    }
                    Purchase.PurchaseState.PENDING -> {
                        // Pending payment - notify user
                        Log.d(TAG, "Subscription pending: ${purchase.products}")
                    }
                }
            }
        } else {
            Log.e(TAG, "Query failed: ${result.billingResult.debugMessage}")
        }
    }
}

ベストプラクティス: アプリがバックグラウンドにある間、または閉じられている間に 発生したサブスクリプションの更新を捕捉するには、 onResume() 内で queryPurchasesAsync() を呼び出します。


商品詳細を照会する

ユーザーに表示する前に、価格、課金期間、オファートークンを含むサブスクリプション商品の 詳細を取得します。

Kotlin
private suspend fun querySubscriptionProducts() {
    val productList = listOf(
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId("premium_monthly")
            .setProductType(BillingClient.ProductType.SUBS)
            .build(),
        QueryProductDetailsParams.Product.newBuilder()
            .setProductId("premium_yearly")
            .setProductType(BillingClient.ProductType.SUBS)
            .build()
    )

    val params = QueryProductDetailsParams.newBuilder()
        .setProductList(productList)
        .build()

    withContext(Dispatchers.IO) {
        val productDetailsResult = billingClient.queryProductDetails(params)

        if (productDetailsResult.billingResult.responseCode == BillingResponseCode.OK) {
            // Process successfully retrieved product details
            productDetailsResult.productDetailsList?.forEach { productDetails ->
                // Extract subscription offers
                productDetails.subscriptionOfferDetails?.forEach { offer ->
                    val price = offer.pricingPhases.pricingPhaseList.firstOrNull()
                    Log.d(TAG, "Product: ${productDetails.productId}, Price: ${price?.formattedPrice}")
                }
            }

            // Handle unfetched products
            productDetailsResult.unfetchedProductList?.forEach { unfetchedProduct ->
                Log.w(TAG, "Unfetched: ${unfetchedProduct.productId}")
            }
        }
    }
}

購入更新を処理する

サブスクリプションイベントを処理する

購入状態の変化を処理し、サブスクリプションのライフサイクルに基づいて適切なイベントを Singular に送信します。

Kotlin
private fun handlePurchaseUpdate(purchase: Purchase) {
    when (purchase.purchaseState) {
        Purchase.PurchaseState.PURCHASED -> {
            if (!purchase.isAcknowledged) {
                // Verify purchase on your backend first
                verifyPurchaseOnBackend(purchase) { isValid ->
                    if (isValid) {
                        // Send subscription event to Singular
                        trackSubscriptionToSingular(purchase)

                        // Grant entitlement to user
                        grantSubscriptionAccess(purchase)

                        // Acknowledge purchase to Google
                        acknowledgePurchase(purchase)
                    }
                }
            }
        }
        Purchase.PurchaseState.PENDING -> {
            // Notify user that payment is pending
            Log.d(TAG, "Purchase pending approval: ${purchase.products}")
            notifyUserPendingPayment(purchase)
        }
    }
}

private fun trackSubscriptionToSingular(purchase: Purchase) {
    // Get cached product details for pricing information
    val productDetails = getCachedProductDetails(purchase.products.first())

    productDetails?.let { details ->
        val subscriptionOffer = details.subscriptionOfferDetails?.firstOrNull()
        val pricingPhase = subscriptionOffer?.pricingPhases?.pricingPhaseList?.firstOrNull()

        val price = pricingPhase?.priceAmountMicros?.div(1_000_000.0) ?: 0.0
        val currency = pricingPhase?.priceCurrencyCode ?: "USD"

        // Determine event name based on purchase type
        val eventName = if (isNewSubscription(purchase)) {
            "sng_subscribe"
        } else {
            "subscription_renewed"
        }

        // Send to Singular WITHOUT receipt
        Singular.customRevenue(
            eventName,
            currency,
            price,
            mapOf(
                "subscription_id" to purchase.products.first(),
                "order_id" to (purchase.orderId ?: ""),
                "purchase_time" to purchase.purchaseTime.toString()
            )
        )

        Log.d(TAG, "Tracked $eventName to Singular: $price $currency")
    }
}

private fun acknowledgePurchase(purchase: Purchase) {
    val acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
        .setPurchaseToken(purchase.purchaseToken)
        .build()

    lifecycleScope.launch {
        val ackResult = withContext(Dispatchers.IO) {
            billingClient.acknowledgePurchase(acknowledgePurchaseParams)
        }

        if (ackResult.responseCode == BillingResponseCode.OK) {
            Log.d(TAG, "Purchase acknowledged successfully")
        } else {
            Log.e(TAG, "Acknowledgement failed: ${ackResult.debugMessage}")
        }
    }
}

重大: サブスクリプションイベントをトラッキングする際に、Google Play のレシートを Singular に送信しないでください。レシート検証なしで customRevenue() メソッドを使用してください。購入は 3 日以内に承認 (acknowledge) してください。 そうしないと自動的に返金されます。


iOS: StoreKit 連携

Apple の StoreKit フレームワークを連携させてサブスクリプションを管理し、iOS アプリ向けに Singular へイベントを送信します。

前提条件

App Store Connect を構成する

アプリで StoreKit を実装する前に、 App Store Connect でサブスクリプション商品とサブスクリプショングループを設定します。

  1. サブスクリプショングループを作成する: アクセスレベルに基づいてサブスクリプションを グループに整理します
  2. サブスクリプション商品を定義する: サブスクリプションのティア、期間、価格を 設定します
  3. オファーを構成する: 導入オファー、プロモーションオファー、サブスクリプション コードを作成します
  4. テスト環境: 開発用にサンドボックスのテストアカウントを設定します

StoreKit を実装する

トランザクションオブザーバーを設定する

サブスクリプションの購入通知や更新を受信するために、 SKPaymentTransactionObserver を実装します。

Swift
import StoreKit

class SubscriptionManager: NSObject, SKPaymentTransactionObserver {

    static let shared = SubscriptionManager()

    private override init() {
        super.init()
        // Add transaction observer
        SKPaymentQueue.default().add(self)
    }

    deinit {
        SKPaymentQueue.default().remove(self)
    }

    // MARK: - SKPaymentTransactionObserver

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for transaction in transactions {
            switch transaction.transactionState {
            case .purchased:
                // New subscription or renewal
                handlePurchasedTransaction(transaction)

            case .restored:
                // Subscription restored from another device
                handleRestoredTransaction(transaction)

            case .failed:
                // Purchase failed
                handleFailedTransaction(transaction)

            case .deferred:
                // Purchase awaiting approval (family sharing)
                print("Transaction deferred: \(transaction.payment.productIdentifier)")

            case .purchasing:
                // Transaction in progress
                break

            @unknown default:
                break
            }
        }
    }

    private func handlePurchasedTransaction(_ transaction: SKPaymentTransaction) {
        // Verify receipt with your backend
        verifyReceipt { isValid in
            if isValid {
                // Send to Singular
                self.trackSubscriptionToSingular(transaction)

                // Grant entitlement
                self.grantSubscriptionAccess(transaction)

                // Finish transaction
                SKPaymentQueue.default().finishTransaction(transaction)
            }
        }
    }

    private func trackSubscriptionToSingular(_ transaction: SKPaymentTransaction) {
        // Get product details for pricing
        let productId = transaction.payment.productIdentifier

        // You should cache product details from SKProductsRequest
        if let product = getCachedProduct(productId) {
            let price = product.price.doubleValue
            let currency = product.priceLocale.currencyCode ?? "USD"

            // Determine event name
            let eventName = isNewSubscription(transaction) ? "sng_subscribe" : "subscription_renewed"

            // Send to Singular WITHOUT receipt
            Singular.customRevenue(
                eventName,
                currency: currency,
                amount: price,
                withAttributes: [
                    "subscription_id": productId,
                    "transaction_id": transaction.transactionIdentifier ?? "",
                    "transaction_date": transaction.transactionDate?.timeIntervalSince1970 ?? 0
                ]
            )
        }
    }
}

サブスクリプションのステータスを照会する

レシート検証を使用して現在のサブスクリプションのステータスを確認し、更新を処理します。

Swift
func refreshSubscriptionStatus() {
    let request = SKReceiptRefreshRequest()
    request.delegate = self
    request.start()
}

extension SubscriptionManager: SKRequestDelegate {
    func requestDidFinish(_ request: SKRequest) {
        // Receipt refreshed successfully
        if let appStoreReceiptURL = Bundle.main.appStoreReceiptURL,
           FileManager.default.fileExists(atPath: appStoreReceiptURL.path) {

            // Send receipt to your backend for validation
            validateReceiptOnServer()
        }
    }

    func request(_ request: SKRequest, didFailWithError error: Error) {
        print("Receipt refresh failed: \(error.localizedDescription)")
    }
}

SDK 連携方式

Singular SDK のカスタム収益メソッドを使用して、レシート検証なしでアプリから サブスクリプションイベントを送信します。

プラットフォーム別の SDK メソッド

サブスクリプションイベントをトラッキングするには、お使いのプラットフォームに適した SDK メソッドを使用します。

Android SDK

Kotlin
// Track subscription without receipt
Singular.customRevenue(
    "sng_subscribe",  // Event name
    "USD",            // Currency
    9.99,             // Amount
    mapOf(            // Additional attributes
        "subscription_id" to "premium_monthly",
        "billing_period" to "monthly"
    )
)

ドキュメント: Android SDK 収益トラッキング


iOS SDK

Swift
// Track subscription without receipt
Singular.customRevenue(
    "sng_subscribe",  // Event name
    currency: "USD",  // Currency
    amount: 9.99,     // Amount
    withAttributes: [ // Additional attributes
        "subscription_id": "premium_monthly",
        "billing_period": "monthly"
    ]
)

ドキュメント: iOS SDK 収益トラッキング


React Native SDK

JavaScript
// Track subscription without receipt
Singular.customRevenueWithArgs(
    "sng_subscribe",  // Event name
    "USD",            // Currency
    9.99,             // Amount
    {                 // Additional attributes
        subscription_id: "premium_monthly",
        billing_period: "monthly"
    }
);

ドキュメント: React Native SDK 収益トラッキング


Unity SDK

C#
// Track subscription without receipt
SingularSDK.CustomRevenue(
    "sng_subscribe",  // Event name
    "USD",            // Currency
    9.99,             // Amount
    new Dictionary<string, object> { // Additional attributes
        { "subscription_id", "premium_monthly" },
        { "billing_period", "monthly" }
    }
);

ドキュメント: Unity SDK 収益トラッキング


Flutter SDK

Dart
// Track subscription without receipt
Singular.customRevenueWithAttributes(
    "sng_subscribe",  // Event name
    "USD",            // Currency
    9.99,             // Amount
    {                 // Additional attributes
        "subscription_id": "premium_monthly",
        "billing_period": "monthly"
    }
);

ドキュメント: Flutter SDK 収益トラッキング


Cordova SDK

JavaScript
// Track subscription without receipt
cordova.plugins.SingularCordovaSdk.customRevenueWithArgs(
    "sng_subscribe",  // Event name
    "USD",            // Currency
    9.99,             // Amount
    {                 // Additional attributes
        subscription_id: "premium_monthly",
        billing_period: "monthly"
    }
);

ドキュメント: Cordova SDK 収益トラッキング

重大: サブスクリプショントラッキングにおいて、IAP (アプリ内購入) メソッドを 使用したり、レシート値を Singular に送信したりしないでください。レシートなしの customRevenue() メソッドのみを使用してください。


サーバー間連携

アプリの状態に依存せず即時にトラッキングできるよう、バックエンドから Singular の REST API にサブスクリプションイベントをリアルタイムで送信します。

実装要件

Singular の Event Endpoint を使用して、安全なバックエンドから収益パラメータ付きの サブスクリプションイベントを送信します。

Event Endpoint

サブスクリプションイベントデータと必要なモバイルデバイスプロパティを付けて、Singular の Event API に POST リクエストを送信します。

エンドポイント:

POST https://api.singular.net/api/v1/evt

必須パラメータ:

  • SDK Key: Singular SDK キー ( a パラメータ)
  • Event Name: サブスクリプションイベント名 ( n パラメータ)
  • Revenue: サブスクリプション金額 ( amt パラメータ)
  • Currency: ISO 4217 通貨コード ( cur パラメータ)
  • Device Identifiers: アトリビューション用の IDFA (iOS) または GAID (Android)
  • Platform: オペレーティングシステム ( p パラメータ)

ドキュメント: Event Endpoint リファレンス および 収益パラメータ


リクエスト例

cURL
curl -X POST 'https://api.singular.net/api/v1/evt' \
  -H 'Content-Type: application/json' \
  -d '{
    "a": "YOUR_SDK_KEY",
    "n": "sng_subscribe",
    "r": 9.99,
    "pcc": "USD",
    "idfa": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
    "p": "iOS",
    "install_time": 1697000000000,
    "extra": {
      "subscription_id": "premium_monthly",
      "order_id": "GPA.1234.5678.9012"
    }
  }'

SDK は不要: サブスクリプションイベントに S2S を使用する場合、これらのイベントを SDK から送信する必要はありません。ただし、ほとんどのアプリはセッショントラッキングおよび サブスクリプション以外のイベントのために SDK も引き続き連携させる必要があります。


サードパーティ連携

サブスクリプションインフラを管理し、イベントを Singular に直接送信するサードパーティ プラットフォームを通じてサブスクリプショントラッキングを構成します。

サポートされているパートナー

Singular サポートが組み込まれているサブスクリプション管理プラットフォームと連携します。

RevenueCat 連携

RevenueCat はサブスクリプションインフラを処理し、サブスクリプションイベントを Singular の REST API に自動的に送信します。

設定手順:

  1. RevenueCat アカウントを作成する: RevenueCat のダッシュボードでアプリを 設定します
  2. Singular 連携を構成する: RevenueCat の連携設定に Singular SDK キーを 追加します
  3. イベントをマッピングする: どの RevenueCat イベントを Singular に送信するかを 構成します
  4. 連携をテストする: イベントが Singular のダッシュボードに表示されることを 確認します

ドキュメント: RevenueCat Singular 連携


Adapty 連携

Adapty はサブスクリプション分析を提供し、その連携を通じてイベントを Singular に送信します。

設定手順:

  1. Adapty アカウントを作成する: Adapty に登録してアプリを構成します
  2. Singular 連携を有効にする: 連携画面に移動して Singular の認証情報を 追加します
  3. イベントマッピングを構成する: どのサブスクリプションイベントを転送するかを 選択します
  4. データフローを確認する: イベントが Singular に正常に届くことを確認します

ドキュメント: Adapty Singular 連携


Superwall 連携

Singular を接続して Superwall のサブスクリプションライフサイクルおよび収益イベントを 受信します。より強力なアトリビューションのために SDID がサポートされています。

設定手順:

  1. Singular SDK を連携させる: セッションおよびイベントトラッキングのために Singular SDK をアプリに追加します
  2. Singular SDID を設定する (任意): Singular SDID を設定し、より強力な アトリビューションのためにアプリ内で SDID を使用して Superwall のユーザー属性を 更新します
  3. Superwall で Singular 連携を追加する: Superwall のダッシュボードで Singular 連携を有効化して構成します

ドキュメント: Superwall Singular 連携


Xsolla 連携

Xsolla は Web Shop での購入に関する情報をモバイルアプリ内イベントとして Singular に 送信します。その後 Singular は、それをモバイルアプリケーションのインストール、 ユーザー獲得、リエンゲージメントキャンペーンにアトリビューションします。

設定手順:

  1. モバイルアプリに Singular を連携させる: Singular SDK をモバイルアプリに 追加します
  2. アプリ内イベントを構成する: Singular にアプリ内イベントを送信します。 たとえば login や、CUID を含むその他のイベントです
  3. Singular パートナー構成を設定する: Xsolla 向けの Singular パートナー 構成を設定します
  4. Xsolla アカウントに Singular を追加する: Xsolla アカウントで Singular を 有効にします

ドキュメント: Xsolla Singular 連携

注: サードパーティ連携はパートナープラットフォームによって管理されます。構成は それぞれのダッシュボードで完了する必要があります。セッションおよびサブスクリプション 以外のイベントのトラッキングのために、Singular SDK も引き続き連携させる必要があります。


サブスクリプションイベントの種類

さまざまなサブスクリプションライフサイクルイベントをトラッキングして、サブスクリプションの 全体的な過程にわたるユーザーエンゲージメント、リテンション、収益を測定します。

標準イベント名

プラットフォーム間で一貫したレポーティングを行うために Singular の標準イベント名を使用するか、 トラッキング要件に基づいてカスタムイベント名を定義します。

サブスクリプションの状態 イベント名 SDK メソッド S2S 連携
新規サブスクリプション sng_subscribe 収益金額付きの customRevenue() (レシートなし) 収益パラメータ付きの Event Endpoint
トライアル開始 sng_start_trial 収益なしの event() メソッド 収益なしの標準 Event Endpoint
トライアル終了 sng_end_trial 収益なしの event() メソッド 収益なしの標準 Event Endpoint
サブスクリプション更新 subscription_renewed 収益金額付きの customRevenue() (レシートなし) 収益パラメータ付きの Event Endpoint
キャンセル subscription_cancelled 収益なしの event() メソッド 収益なしの標準 Event Endpoint
返金 subscription_refunded 負の金額付きの customRevenue() (任意)、または収益なしの event() 負の収益付きの Event Endpoint (任意)、または標準イベント

重要: customRevenue() を介してサブスクリプションイベントを送信する際は、 isRestored false のイベントのみを送信してください。復元された購入は既存のサブスクリプションを 表すものであり、新規収益ではありません。


イベント実装例

新規サブスクリプション

ユーザーが初めて新しいサブスクリプションを購入したときにトラッキングします。

Kotlin
// New subscription purchase
Singular.customRevenue(
    "sng_subscribe",
    "USD",
    9.99,
    mapOf(
        "subscription_tier" to "premium",
        "billing_period" to "monthly",
        "product_id" to "premium_monthly"
    )
)

トライアル開始

ユーザーが無料トライアル期間を開始したときに、収益なしでトラッキングします。

Kotlin
// Trial start (no revenue)
Singular.event(
    "sng_start_trial",
    mapOf(
        "trial_duration" to "7_days",
        "subscription_tier" to "premium"
    )
)

サブスクリプション更新

自動サブスクリプション更新を収益金額付きでトラッキングします。

Kotlin
// Subscription renewal
Singular.customRevenue(
    "subscription_renewed",
    "USD",
    9.99,
    mapOf(
        "subscription_tier" to "premium",
        "renewal_count" to 3,
        "product_id" to "premium_monthly"
    )
)

キャンセル

ユーザーがサブスクリプションをキャンセルしたときにトラッキングします (収益イベントなし)。

Kotlin
// Subscription cancelled (no revenue)
Singular.event(
    "subscription_cancelled",
    mapOf(
        "cancellation_reason" to "too_expensive",
        "days_active" to 45,
        "subscription_tier" to "premium"
    )
)

SKAdNetwork 測定

iOS のプライバシーに準拠したアトリビューションのために、Apple の SKAdNetwork を通じて サブスクリプションイベントを測定します。

SKAN サブスクリプションサポート

Singular はすべての連携方式 (SDK、S2S、サードパーティ) を介してサブスクリプション イベントデータを測定できます。イベントの測定は、SKAN のバージョンに応じて、SKAN の postback タイミングウィンドウによって制限される場合があります。

ハイブリッド SKAN 実装

ライフサイクルイベントに Singular SDK を使用し、サブスクリプションに S2S/サードパーティ 連携を使用するアプリでは、両方のデータソースを組み合わせるためにハイブリッド SKAN を 有効にします。

サポートへのお問い合わせ: アプリでハイブリッド SKAN を有効にするには、 担当のカスタマーサクセスマネージャー (CSM) にお問い合わせください。これにより、 バックエンドソースからのサブスクリプションイベントが SKAN の conversion value に 適切にアトリビューションされます。

SKAN Postback ウィンドウ:

  • SKAN 4.0: 3 つの postback ウィンドウ (0~2 日、3~7 日、8~35 日) により、 初期のサブスクリプションイベントや短期の更新を測定できます
  • SKAN 3.0: 単一の 24 時間 postback ウィンドウにより、測定は即時の サブスクリプションのみに制限されます
  • Conversion Value: 価値の高いイベントを優先するために、SKAN の conversion value スキーマにサブスクリプションイベントを構成します

ベストプラクティスと検証

実装のベストプラクティスに従い、本番環境へのデプロイ前にサブスクリプショントラッキングが 正しく機能していることを確認します。

実装のベストプラクティス

主なガイドライン

  • レシート検証を行わない: サブスクリプションをトラッキングする際に、 プラットフォームのレシート (Google Play、App Store) を Singular に決して送信しないでください。 レシートなしで customRevenue() を使用します
  • 復元された購入をフィルタリングする: 収益の重複レポーティングを避けるために、 isRestored が false のイベントのみを送信します
  • バックエンドでの検証: Singular にイベントを送信する前に、必ず安全な バックエンドで購入を検証します
  • 速やかな承認 (acknowledge): 自動返金を防ぐために、Google Play の購入は 3 日以内に承認します
  • Resume 時に照会する: アプリが閉じている間に発生した更新を捕捉するために、 onResume() 内で queryPurchasesAsync() を呼び出します
  • 一貫したイベント名: 統一されたレポーティングのために、標準の Singular イベント名 (sng_subscribe、subscription_renewed) を使用します
  • メタデータを含める: 詳細な分析のために、subscription_tier、 billing_period、product_id などの属性を追加します

テストと検証

テストチェックリスト

  1. テスト環境の設定: Google Play と App Store の両方で、サンドボックス/ テストアカウントを使用します
  2. 新規サブスクリプション: sng_subscribe イベントが適切な金額と通貨で 正しく送信されることを確認します
  3. トライアルフロー: トライアル開始とトライアル終了のイベントを収益なしで テストします
  4. 更新処理: 更新によって subscription_renewed イベントが正しい収益で トリガーされることを確認します
  5. キャンセルトラッキング: ユーザーがサブスクリプションをキャンセルしたときに キャンセルイベントが発火することを確認します
  6. 復元のフィルタリング: 復元された購入が重複イベントを送信しないことを 確認します
  7. Singular ダッシュボード: イベントが正しいアトリビューションで Singular に 表示されることを確認します
  8. クロスデバイス: 複数のデバイス間でサブスクリプションが同期されることを テストします

よくある問題

  • イベントの欠落: billing client の接続がアクティブであり、アプリの resume 時に queryPurchasesAsync() が呼び出されていることを確認します
  • イベントの重複: 復元された購入が isRestored チェックで適切に フィルタリングされていることを確認します
  • 誤った収益: ProductDetails からの価格抽出が、micros を 1,000,000 で 正しく除算していることを確認します
  • アトリビューションの問題: デバイス識別子 (IDFA/GAID) が S2S リクエストに 適切に含まれていることを確認します
  • 承認 (acknowledge) の失敗: 承認の前にバックエンドのレシート検証が 完了していることを確認します

サポートリソース: トラブルシューティングのサポートについては、SDK バージョン、 プラットフォームの詳細、サンプルイベントペイロード、問題を示す Singular ダッシュボードの スクリーンショットを添えて Singular サポートにお問い合わせください。