구독 이벤트 추적 구현 가이드
Singular의 SDK, 서버 간 연동 또는 타사 파트너를 사용하여 모든 플랫폼에서 구독 구매, 사용자 유지율 및 캠페인 성과를 측정할 수 있는 포괄적인 구독 추적을 구현하세요.
Singular는 신규 구독, 갱신, 평가판, 취소, 환불을 포함한 구독 이벤트를 사용자를 생성한 마케팅 캠페인에 정확하게 어트리뷰션하여 구독 라이프사이클과 구매 창출에 대한 완벽한 가시성을 제공합니다.
구현 옵션
앱 아키텍처와 비즈니스 요구 사항에 가장 적합한 구현 방법을 선택하세요.
| 고려 사항 | SDK 연동 | 서버 대 서버(S2S) | 타사 연동 |
|---|---|---|---|
| 구현 | Singular SDK에서 사용자 지정 구매 방법을 통해 구독 이벤트를 전송하세요. | 필수 모바일 기기 속성을 사용하여 백엔드에서 Singular의 REST API로 이벤트를 전송하세요. | 리베이트캣, 어댑티 또는 기타 구독 플랫폼과의 연동을 구성합니다. |
| 앱 활동 종속성 | SDK가 이벤트를 전송하려면 앱이 실행되어야 합니다. | 앱 상태와 무관하게 실시간으로 전송되는 이벤트 | 파트너 플랫폼에서 실시간으로 전송되는 이벤트 |
| 이벤트 타이밍 | 이벤트 시간은 앱 실행에 따라 실제 구독 시간과 다를 수 있습니다. | 백엔드에서 처리되는 실시간 이벤트 추적 | 파트너 처리 후 실시간에 가까운 추적 |
| 복잡성 | 간단한 클라이언트 측 구현 | 안전한 백엔드 인프라 필요 | 파트너 계정 및 구성 필요 |
중요: 구독 이벤트에 S2S 또는 타사 연동을 사용하는 대부분의 고객은 완전한 어트리뷰션 추적을 위해 세션 및 비구독 이벤트를 관리하기 위해 여전히 Singular SDK를 연동해야 합니다.
Android: 구글 플레이 청구 연동
구글 플레이 빌링 라이브러리 8.0.0을 연동하여 기기에서 직접 구독 정보를 쿼리하고 구독 상태를 관리하여 Singular SDK를 연동할 수 있습니다.
전제 조건
Google Play 콘솔 구성
앱에서 청구를 구현하기 전에 Google Play 콘솔에서인앱 제품 및 구독 항목을 설정하세요.
- 구독 제품을 생성합니다: Play 콘솔에서 구독 티어, 청구 기간 및 가격을 정의합니다.
- 오퍼 구성: 기본 요금제, 오퍼 토큰 및 프로모션 가격을 설정합니다.
- 테스트 설정: 테스트 계정 생성 및 제품 가용성 확인
청구 라이브러리 종속성 추가
build.gradle 업데이트
코루틴 지원을 위해 Kotlin 확장을 사용하여 앱의 종속성에 Google Play 청구 라이브러리 8.0.0을 추가하세요.
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 및 향상된 보류 중인 트랜잭션 처리가 도입되었습니다.
청구 클라이언트 초기화
청구 관리자 생성
청구 관리자 클래스를 설정하여 모든 구독 작업을 처리하고 이벤트 추적을 위해 Singular SDK와 연동하세요.
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() 을 사용하여 기존 구독을 쿼리하여 앱 외부에서 구매한 갱신 및 구독을 처리합니다.
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()으로 전화하여 앱이 백그라운드 상태이거나 종료된 상태에서 발생한 구독 갱신을 확인합니다.
제품 세부 정보 조회
사용자에게 표시하기 전에 가격, 청구 기간, 오퍼 토큰을 포함한 구독 제품 세부 정보를 검색합니다.
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에 전송합니다.
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일 이내에 구매를 승인하지 않으면 자동으로 환불됩니다.
iOS: 스토어키트 연동
Apple의 스토어키트 프레임워크를 연동하여 iOS 앱용 Singular에 구독을 관리하고 이벤트를 전송하세요.
전제 조건
앱 스토어 연결 구성
앱에서 스토어키트를 구현하기 전에 앱 스토어 커넥트에서구독 제품 및 구독 그룹을 설정하세요.
- 구독 그룹을 생성합니다: 액세스 수준에 따라 구독을 그룹으로 구성합니다.
- 구독 제품 정의: 구독 등급, 기간 및 가격 설정: 구독 등급, 기간 및 가격 설정
- 오퍼 구성: 소개 오퍼, 프로모션 오퍼 및 구독 코드 만들기
- 테스트 환경: 개발용 샌드박스 테스트 계정 설정
StoreKit 구현
거래 옵저버 설정
SKPaymentTransactionObserver 을 구현하여 구독 구매 알림 및 갱신을 받습니다.
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
]
)
}
}
}
구독 상태 조회
영수증 유효성 검사를 사용하여 현재 구독 상태를 확인하고 갱신을 처리합니다.
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 방법을 사용하여 구독 이벤트를 추적하세요.
안드로이드 SDK
// 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"
)
)
iOS SDK
// 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
// Track subscription without receipt
Singular.customRevenueWithArgs(
"sng_subscribe", // Event name
"USD", // Currency
9.99, // Amount
{ // Additional attributes
subscription_id: "premium_monthly",
billing_period: "monthly"
}
);
Unity SDK
// 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
// Track subscription without receipt
Singular.customRevenueWithAttributes(
"sng_subscribe", // Event name
"USD", // Currency
9.99, // Amount
{ // Additional attributes
"subscription_id": "premium_monthly",
"billing_period": "monthly"
}
);
Cordova SDK
// 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"
}
);
중요: 구독 추적을 위해 IAP(인앱 구매) 방법을 사용하거나 영수증 값을 Singular로 보내지 마세요. 영수증 없이 customRevenue() 방법만 사용하세요.
서버 간 연동
앱 상태와 관계없이 즉각적인 추적을 위해 백엔드에서 Singular의 REST API로 구독 이벤트를 실시간으로 전송하세요.
구현 요구 사항
Singular의 이벤트 엔드포인트를 사용하여 보안 백엔드에서 구매 매개변수와 함께 구독 이벤트를 전송하세요.
이벤트 엔드포인트
구독 이벤트 데이터와 필수 모바일 디바이스 속성을 포함한 POST 요청을 Singular의 이벤트 API로 전송합니다.
엔드포인트:
POST https://api.singular.net/api/v1/evt
필수 파라미터:
-
SDK 키: Singular SDK 키(
a파라미터) -
이벤트 이름: 구독 이벤트 이름(
n파라미터) -
구매: 구독 금액(
amt파라미터) -
통화: ISO 4217 통화 코드(
cur파라미터) - 기기 식별자: 어트리뷰션용 IDFA(iOS) 또는 GAID(Android)
-
플랫폼: 운영 체제 (
p파라미터)
문서: 이벤트 엔드포인트 참조및 구매 파라미터
요청 예시
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로 전송합니다.
설정 단계:
- 리베이트캣 계정을 생성합니다: 리베이트캣 대시보드에서 앱을 설정합니다.
- Singular 연동을 구성합니다: 리베이트캣 연동 설정에서 Singular SDK 키를 추가합니다.
- 이벤트 매핑: 이벤트 매핑: Singular로 전송할 리베이트캣 이벤트를 구성합니다.
- 연동 테스트: 이벤트가 Singular 대시보드에 표시되는지 확인합니다.
Adapty 연동
Adapty는 구독 분석을 제공하고 연동을 통해 이벤트를 Singular로 보냅니다.
설정 단계:
- Adapty 계정을 생성합니다: Adapty에서 앱을 등록하고 구성합니다.
- Singular 연동을 활성화합니다: 연동으로 이동하여 Singular 자격 증명을 추가합니다.
- 이벤트 매핑을 구성합니다: 전달할 구독 이벤트 선택: 전달할 구독 이벤트 선택
- 데이터 흐름 확인: 이벤트가 Singular에 성공적으로 도달하는지 확인합니다.
참고: 타사 연동 서비스는 파트너 플랫폼에서 관리합니다. 해당 대시보드에서 구성을 완료해야 합니다. 세션 및 비구독 이벤트 추적을 위해서는 여전히 Singular SDK를 연동해야 합니다.
구독 이벤트 유형
다양한 구독 라이프사이클 이벤트를 추적하여 전체 구독 여정에서 사용자 참여, 유지율, 구매을 측정하세요.
표준 이벤트 이름
여러 플랫폼에서 일관된 보고를 위해 Singular의 표준 이벤트 이름을 사용하거나 추적 요구사항에 따라 사용자 지정 이벤트 이름을 정의하세요.
| 구독 상태 | 이벤트 이름 | SDK 방법 | S2S 연동 |
|---|---|---|---|
| 새 구독 |
sng_subscribe
|
customRevenue(), 구매 금액 포함(영수증 없음) | 구매 매개변수가 있는 이벤트 엔드포인트 |
| 평가판 시작 |
sng_start_trial
|
구매이 없는 이벤트() 메서드 | 구매이 없는 표준 이벤트 엔드포인트 |
| 평가판 종료 |
sng_end_trial
|
구매이 없는 이벤트() 메서드 | 구매이 없는 표준 이벤트 엔드포인트 |
| 구독 갱신 |
subscription_renewed
|
구매 금액이 있는 customRevenue() 메서드(영수증 없음) | 구매 매개변수가 있는 이벤트 엔드포인트 |
| 취소 |
subscription_cancelled
|
구매이 없는 이벤트() 메서드 | 구매이 없는 표준 이벤트 엔드포인트 |
| 환불 |
subscription_refunded
|
구매이 음수인 customRevenue()(선택 사항) 또는 구매이 없는 이벤트() | 구매이 음수인 이벤트 엔드포인트(선택 사항) 또는 표준 이벤트 |
중요: customRevenue()를 통해 구독 이벤트를 전송할 때는 isRestored 이 거짓인 이벤트만 전송하세요. 복원된 구매는 새로운 구매이 아닌 기존 구독을 나타냅니다.
이벤트 구현 예시
새 구독
사용자가 처음으로 새 정기구독을 구매하는 시기를 추적합니다.
// New subscription purchase
Singular.customRevenue(
"sng_subscribe",
"USD",
9.99,
mapOf(
"subscription_tier" to "premium",
"billing_period" to "monthly",
"product_id" to "premium_monthly"
)
)
평가판 시작
사용자가 구매 없이 무료 평가판 기간을 시작하는 시기를 추적합니다.
// Trial start (no revenue)
Singular.event(
"sng_start_trial",
mapOf(
"trial_duration" to "7_days",
"subscription_tier" to "premium"
)
)
구독 갱신
구매 금액과 함께 자동 구독 갱신을 추적합니다.
// Subscription renewal
Singular.customRevenue(
"subscription_renewed",
"USD",
9.99,
mapOf(
"subscription_tier" to "premium",
"renewal_count" to 3,
"product_id" to "premium_monthly"
)
)
취소
사용자가 구독을 취소하는 시점을 추적합니다(구매 이벤트 없음).
// 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 포스트백 타이밍 윈도우에 의해 제한될 수 있습니다.
하이브리드 SKAN 구현
라이프사이클 이벤트에는 Singular SDK를 사용하고 구독에는 S2S/타사 연동을 사용하는 앱의 경우 하이브리드 SKAN을 활성화하여 두 데이터 소스를 결합할 수 있습니다.
지원팀에 문의하세요: 고객 성공 매니저(CSM)에게 연락하여 앱에 Hybrid SKAN을 사용하도록 설정하세요. 이렇게 하면 백엔드 소스의 구독 이벤트가 SKAN 전환 값에 올바르게 어트리뷰션됩니다.
SKAN 포스트백 윈도우:
- SKAN 4.0: 세 가지 포스트백 윈도우(0-2일, 3-7일, 8-35일)를 통해 초기 구독 이벤트와 단기 갱신을 측정할 수 있습니다.
- SKAN 3.0: 24시간의 Singular 포스트백 윈도우로 즉각적인 구독으로만 측정이 제한됩니다.
- 전환 값: SKAN 전환 값 스키마에서 구독 이벤트를 구성하여 가치가 높은 이벤트에 우선순위를 부여합니다.
모범 사례 및 검증
프로덕션 배포 전에 구현 모범 사례를 따르고 구독 추적이 올바르게 작동하는지 확인하세요.
구현 모범 사례
주요 가이드라인
- 영수증 유효성 검사 금지: 구독 추적 시 플랫폼 영수증(구글 플레이, 앱 스토어)을 Singular로 보내지 말고 영수증 없이 customRevenue()를 사용하세요.
- 복원된 구매 필터링: 중복 구매 보고를 방지하기 위해 isRestored가 거짓인 경우에만 이벤트를 전송합니다.
- 백엔드 확인: 이벤트를 Singular로 보내기 전에 항상 보안 백엔드에서 구매를 확인합니다.
- 시기적절한 승인: 자동 환불을 방지하기 위해 3일 이내에 Google Play 구매를 승인합니다.
- 재개 시 쿼리: 앱이 닫혀 있는 동안 발생한 갱신을 포착하기 위해 onResume()에서 queryPurchasesAsync()를 호출합니다.
- 일관된 이벤트 이름: 연동 보고를 위해 표준 Singular 이벤트 이름(sng_subscribe, subscription_renewed)을 사용하세요.
- 메타데이터 포함: 자세한 분석을 위해 구독_티어, 청구_기간, 제품_ID와 같은 속성을 추가합니다.
테스트 및 유효성 검사
테스트 체크리스트
- 테스트 환경 설정: 구글 플레이와 앱스토어 모두에 샌드박스/테스트 계정 사용
- 새 구독: sng_subscribe 이벤트가 적절한 금액과 통화로 올바르게 전송되는지 확인합니다.
- 평가판 흐름: 구매이 없는 평가판 시작 및 평가판 종료 이벤트 테스트
- 갱신 처리: 갱신 시 올바른 구매으로 구독_갱신 이벤트가 트리거되는지 확인합니다.
- 취소 추적: 사용자가 구독을 취소할 때 취소 이벤트가 실행되는지 확인합니다.
- 복원 필터링: 복원 필터링: 복원된 구매가 중복 이벤트를 보내지 않는지 확인합니다.
- Singular 대시보드: 이벤트가 올바른 어트리뷰션으로 Singular에 표시되는지 확인하세요.
- 크로스 디바이스: 여러 기기에서 구독 동기화 테스트
일반적인 문제
- 누락된 이벤트: 청구 클라이언트 연결이 활성화되어 있고 앱 재개 시 queryPurchasesAsync()가 호출되는지 확인합니다.
- 중복 이벤트: 복원된 구매가 isRestored 확인을 통해 올바르게 필터링되었는지 확인
- 잘못된 구매: ProductDetails에서 가격 추출이 마이크로를 1,000,000으로 올바르게 나누는지 확인합니다.
- 어트리뷰션 문제: 디바이스 식별자(IDFA/GAID)가 S2S 요청에 올바르게 포함되었는지 확인
- 승인 실패: 승인 전에 백엔드 영수증 유효성 검사가 완료되었는지 확인
지원 리소스: 문제 해결 지원이 필요하면 SDK 버전, 플랫폼 세부 정보, 샘플 이벤트 페이로드, 문제를 보여주는 Singular 대시보드 스크린샷을 가지고 Singular 지원팀에 문의하세요.