광고 매출 어트리뷰션
미디에이션 플랫폼에서 발생한 광고 매출을 트래킹하고 사용자를 획득한 마케팅 캠페인에 귀속시켜, 캠페인 비용, 인앱 구매, 광고 구매화 전반에 걸친 완전한 ROI 가시성을 제공합니다.
개요
광고 매출 어트리뷰션이란
광고 매출 어트리뷰션은 모바일 앱 광고 매출을 앱 설치를 유도한 사용자 획득 캠페인과 연결하여, 광고 구매화를 포함한 캠페인의 실제 구매성을 측정할 수 있도록 합니다.
주요 이점:
- 연동 ROI 뷰: 캠페인 비용, 인앱 매출, 광고 매출을 하나의 대시보드에서 확인
- 캠페인 최적화: 광고 매출 데이터를 광고 네트워크로 전송하여 입찰 및 타겟팅 개선
- LTV 측정: 광고 구매화를 포함한 사용자 전체 라이프타임 밸류 계산
데이터 소스: 광고 매출 데이터는 일반적으로 미디에이션 플랫폼(예: AdMob, AppLovin MAX, IronSource)에서 사용자 단위 또는 임프레션 단위로 제공됩니다. Singular은 이러한 데이터를 수신하기 위한 여러 연동 방식을 지원합니다.
자세히 알아보기: 설정, 리포팅, 트러블슈팅에 대한 자세한 내용은 광고 매출 어트리뷰션 FAQ 를 참조하세요.
구현 요구 사항
필수 가이드라인
필수 SingularAdData 파라미터:
생성자 인자
adPlatform
,
currency
,
revenue
는 모두 필수입니다. 누락되거나 비어 있거나 유효하지 않으면 SDK는 예외를 발생시키지 않고 오류도 반환하지 않은 채
Singular.adRevenue()
호출을 자동으로 폐기합니다.
Singular.adRevenue()
는 SDK가 아직 초기화되지 않은 경우에도 자동으로 무시되므로, 호출 전 반드시
Singular.init()
이 실행되었는지 확인하세요.
데이터 정확성이 중요합니다:
- 통화 코드: 세 글자로 된 ISO 4217 통화 코드(예: USD, EUR, INR)를 사용하세요. 많은 미디에이션 플랫폼이 USD로 보고하므로, 구현 전 플랫폼의 통화를 확인하세요.
-
전송 전 검증:
Singular.adRevenue()를 호출하기 전에 항상 매출과 통화 데이터를 검증하세요. 잘못된 데이터는 제출 후 수정할 수 없습니다. - 플랫폼별 차이점: 일부 SDK는 매출을 micros 단위로 보고하고(1,000,000으로 나누어야 함), 다른 SDK는 표준 통화 단위로 보고합니다. 플랫폼 문서를 확인하세요.
설정 단계
광고 매출 어트리뷰션을 구현하려면 다음 단계를 따르세요:
- SDK 업데이트: 최신 Singular SDK 버전을 사용하고 있는지 확인하세요.
- 연동 방식 선택: 아래에서 사용 중인 환경에 맞는 미디에이션 플랫폼 연동을 선택하세요.
- 콜백 구현: 매출 데이터를 캡처하기 위해 플랫폼별 페이드 이벤트 리스너를 추가하세요.
- 데이터 검증: 매출 리포팅을 테스트하고 Singular 대시보드에 데이터가 표시되는지 확인하세요.
플랫폼 연동
AdMob 연동
임프레션 단위 매출 리포팅을 위해 페이드 이벤트 리스너를 사용하여 Google AdMob의 광고 매출을 트래킹합니다.
요구 사항:
플랫폼별 매출 리포팅:
AdMob은 플랫폼별로 매출을 다르게 보고합니다. Android는 매출을 micros 단위(예: $0.005 = 5000)로 반환합니다. Singular로 전송하기 전에
adValue.valueMicros
를 1,000,000으로 나누어 표준 통화 단위로 변환하세요.
구현
광고를 로드할 때 페이드 이벤트 리스너를 설정하여 매출 데이터를 캡처하고 Singular로 전송합니다.
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.rewarded.RewardedAd
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback
private const val AD_UNIT_ID = "YOUR_AD_UNIT_ID"
class AdManager(private val context: Context) {
private var rewardedAd: RewardedAd? = null
fun loadRewardedAd() {
val adRequest = AdRequest.Builder().build()
RewardedAd.load(context, AD_UNIT_ID, adRequest,
object : RewardedAdLoadCallback() {
override fun onAdLoaded(ad: RewardedAd) {
rewardedAd = ad
// Set full screen content callback
rewardedAd?.fullScreenContentCallback =
object : FullScreenContentCallback() {
override fun onAdShowedFullScreenContent() {
Log.d("AdManager", "Rewarded ad displayed")
}
override fun onAdDismissedFullScreenContent() {
Log.d("AdManager", "Rewarded ad dismissed")
}
}
// Set paid event listener for revenue tracking
rewardedAd?.setOnPaidEventListener { adValue ->
// Convert revenue from micros to standard units
val revenue = adValue.valueMicros / 1_000_000.0
val currency = adValue.currencyCode
// Validate revenue data before sending
if (revenue > 0 && !currency.isNullOrEmpty()) {
val data = SingularAdData(
"AdMob",
currency,
revenue
)
Singular.adRevenue(data)
Log.d("AdManager", "Ad revenue sent: $revenue $currency")
} else {
Log.w("AdManager", "Invalid revenue: $revenue $currency")
}
}
}
override fun onAdFailedToLoad(error: LoadAdError) {
Log.e("AdManager", "Ad failed to load: ${error.message}")
}
})
}
}
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
import com.google.android.gms.ads.AdRequest;
import com.google.android.gms.ads.rewarded.RewardedAd;
import com.google.android.gms.ads.rewarded.RewardedAdLoadCallback;
private static final String AD_UNIT_ID = "YOUR_AD_UNIT_ID";
public class AdManager {
private RewardedAd rewardedAd;
private Context context;
public AdManager(Context context) {
this.context = context;
}
public void loadRewardedAd() {
AdRequest adRequest = new AdRequest.Builder().build();
RewardedAd.load(context, AD_UNIT_ID, adRequest,
new RewardedAdLoadCallback() {
@Override
public void onAdLoaded(RewardedAd ad) {
rewardedAd = ad;
// Set full screen content callback
rewardedAd.setFullScreenContentCallback(
new FullScreenContentCallback() {
@Override
public void onAdShowedFullScreenContent() {
Log.d("AdManager", "Rewarded ad displayed");
}
@Override
public void onAdDismissedFullScreenContent() {
Log.d("AdManager", "Rewarded ad dismissed");
}
});
// Set paid event listener for revenue tracking
rewardedAd.setOnPaidEventListener(new OnPaidEventListener() {
@Override
public void onPaidEvent(AdValue adValue) {
// Convert revenue from micros to standard units
double revenue = adValue.getValueMicros() / 1_000_000.0;
String currency = adValue.getCurrencyCode();
// Validate revenue data before sending
if (revenue > 0 && currency != null && !currency.isEmpty()) {
SingularAdData data = new SingularAdData(
"AdMob",
currency,
revenue
);
Singular.adRevenue(data);
Log.d("AdManager", "Ad revenue sent: " + revenue + " " + currency);
} else {
Log.w("AdManager", "Invalid revenue: " + revenue + " " + currency);
}
}
});
}
@Override
public void onAdFailedToLoad(LoadAdError error) {
Log.e("AdManager", "Ad failed to load: " + error.getMessage());
}
});
}
}
AppLovin MAX 연동
AppLovin Impression-Level User Revenue API를 사용하여 임프레션 단위 광고 매출을 공유합니다.
요구 사항:
- AppLovin MAX SDK 구현 ( Impression-Level Revenue API 가이드 참조)
-
AppLovin Communicator를 통해
max_revenue_events토픽을 구독
구현
AppLovin Communicator 콜백을 통해 매출 메시지를 처리합니다.
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.applovin.communicator.AppLovinCommunicatorMessage
override fun onMessageReceived(message: AppLovinCommunicatorMessage) {
// Check for revenue events topic
if (message.topic == "max_revenue_events") {
val adData: Bundle? = message.messageData
// Extract and validate revenue value
val revenueValue = adData?.getDouble("revenue", 0.0) ?: 0.0
if (revenueValue > 0) {
val data = SingularAdData(
"AppLovin",
"USD", // AppLovin typically reports in USD
revenueValue
)
Singular.adRevenue(data)
Log.d("AppLovin", "Ad revenue sent: $revenueValue USD")
} else {
Log.w("AppLovin", "Invalid revenue value: $revenueValue")
}
}
}
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
import com.applovin.communicator.AppLovinCommunicatorMessage;
@Override
public void onMessageReceived(AppLovinCommunicatorMessage message) {
// Check for revenue events topic
if ("max_revenue_events".equals(message.getTopic())) {
Bundle adData = message.getMessageData();
// Extract and validate revenue value
double revenueValue = (adData != null) ?
adData.getDouble("revenue", 0.0) : 0.0;
if (revenueValue > 0) {
SingularAdData data = new SingularAdData(
"AppLovin",
"USD", // AppLovin typically reports in USD
revenueValue
);
Singular.adRevenue(data);
Log.d("AppLovin", "Ad revenue sent: " + revenueValue + " USD");
} else {
Log.w("AppLovin", "Invalid revenue value: " + revenueValue);
}
}
}
Unity LevelPlay (IronSource) 연동
IronSource SDK를 사용하여 ironSource 및 미디에이션 네트워크의 임프레션 단위 매출을 트래킹합니다.
요구 사항:
- ironSource SDK 구현 ( 시작 가이드 참조)
- IronSource 대시보드에서 ARM SDK Postbacks 플래그 활성화
- 매출 콜백을 수신하도록 임프레션 데이터 리스너 설정
자세히 알아보기: 자세한 설정 내용은 IronSource 광고 매출 문서 를 참조하세요.
구현
임프레션 데이터 성공 콜백을 구현하여 매출을 캡처하고 전송합니다.
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.ironsource.mediationsdk.impressionData.ImpressionData
fun onImpressionDataSuccess(impressionData: ImpressionData?) {
// Validate impression data
if (impressionData == null) {
Log.d("IronSource", "No impression data available")
return
}
// Extract and validate revenue
val revenue = impressionData.revenue?.toDouble() ?: 0.0
if (revenue <= 0) {
Log.w("IronSource", "Invalid revenue: $revenue")
return
}
// Create and send ad revenue data
val data = SingularAdData(
"IronSource",
"USD", // IronSource typically reports in USD
revenue
)
Singular.adRevenue(data)
Log.d("IronSource", "Ad revenue sent: $revenue USD")
}
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
import com.ironsource.mediationsdk.impressionData.ImpressionData;
public void onImpressionDataSuccess(ImpressionData impressionData) {
// Validate impression data
if (impressionData == null) {
Log.d("IronSource", "No impression data available");
return;
}
// Extract and validate revenue
double revenue = (impressionData.getRevenue() != null) ?
impressionData.getRevenue().doubleValue() : 0.0;
if (revenue <= 0) {
Log.w("IronSource", "Invalid revenue: " + revenue);
return;
}
// Create and send ad revenue data
SingularAdData data = new SingularAdData(
"IronSource",
"USD", // IronSource typically reports in USD
revenue
);
Singular.adRevenue(data);
Log.d("IronSource", "Ad revenue sent: " + revenue + " USD");
}
TradPlus 연동
글로벌 임프레션 리스너를 사용하여 TradPlus 미디에이션의 광고 매출을 캡처합니다.
요구 사항:
-
TradPlusSdk.setGlobalImpressionListener()를 통해 글로벌 임프레션 리스너 설정 -
onImpressionSuccess콜백을 처리하여 매출 데이터 수신 - eCPM을 밀리 단위에서 표준 통화 단위로 변환 (1000으로 나누기)
구현
글로벌 임프레션 리스너를 등록하여 모든 광고 임프레션과 매출을 트래킹합니다.
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
import com.tradplus.ads.mgr.GlobalImpressionManager
import com.tradplus.ads.base.bean.TPAdInfo
// Set global impression listener
TradPlusSdk.setGlobalImpressionListener(
object : GlobalImpressionManager.GlobalImpressionListener {
override fun onImpressionSuccess(tpAdInfo: TPAdInfo?) {
// Validate ad info
if (tpAdInfo == null) {
Log.w("TradPlus", "AdInfo is null")
return
}
// Convert eCPM from milli-units to dollars
val ecpm = tpAdInfo.ecpm ?: run {
Log.w("TradPlus", "eCPM is null")
return
}
val revenue = ecpm.toDouble() / 1000.0
// Validate revenue
if (revenue <= 0) {
Log.w("TradPlus", "Invalid revenue: $revenue")
return
}
// Create and send ad revenue data
val data = SingularAdData(
"TradPlus",
"USD",
revenue
)
Singular.adRevenue(data)
Log.d("TradPlus", "Ad revenue sent: $revenue USD")
}
}
)
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
import com.tradplus.ads.mgr.GlobalImpressionManager;
import com.tradplus.ads.base.bean.TPAdInfo;
// Set global impression listener
TradPlusSdk.setGlobalImpressionListener(
new GlobalImpressionManager.GlobalImpressionListener() {
@Override
public void onImpressionSuccess(TPAdInfo tpAdInfo) {
// Validate ad info
if (tpAdInfo == null) {
Log.w("TradPlus", "AdInfo is null");
return;
}
// Validate eCPM is not null
if (tpAdInfo.getEcpm() == null) {
Log.w("TradPlus", "eCPM is null");
return;
}
// Convert eCPM from milli-units to dollars
double revenue = tpAdInfo.getEcpm().doubleValue() / 1000.0;
// Validate revenue
if (revenue <= 0) {
Log.w("TradPlus", "Invalid revenue: " + revenue);
return;
}
// Create and send ad revenue data
SingularAdData data = new SingularAdData(
"TradPlus",
"USD",
revenue
);
Singular.adRevenue(data);
Log.d("TradPlus", "Ad revenue sent: " + revenue + " USD");
}
}
);
범용 연동 (기타 플랫폼)
범용
SingularAdData
인터페이스를 사용하여 모든 미디에이션 플랫폼을 연동할 수 있습니다.
요구 사항:
- 미디에이션 플랫폼에서 제공하는 임프레션 단위 매출 데이터에 액세스
- 표준 통화 단위(micros 아님)의 매출 금액
- ISO 4217 통화 코드 (예: USD, EUR, INR)
데이터 정확성: Singular로 전송하기 전에 매출과 통화 데이터를 검증하세요. 잘못된 데이터는 제출 후 수정할 수 없습니다.
구현
플랫폼 이름, 통화, 매출과 함께
SingularAdData
객체를 생성한 후
Singular.adRevenue()
를 호출합니다.
import com.singular.sdk.Singular
import com.singular.sdk.SingularAdData
fun reportAdRevenue(adPlatform: String, currency: String, revenue: Double) {
// Validate revenue
if (revenue <= 0) {
Log.w("AdRevenue", "Invalid revenue: $revenue")
return
}
// Validate currency
if (currency.isEmpty()) {
Log.w("AdRevenue", "Invalid currency: $currency")
return
}
// Create ad revenue data object
val data = SingularAdData(
adPlatform,
currency,
revenue
)
// Send to Singular
Singular.adRevenue(data)
Log.d("AdRevenue", "Revenue sent: $revenue $currency from $adPlatform")
}
// Example usage
reportAdRevenue("MyMediationPlatform", "USD", 0.05)
import com.singular.sdk.Singular;
import com.singular.sdk.SingularAdData;
public void reportAdRevenue(String adPlatform, String currency, double revenue) {
// Validate revenue
if (revenue <= 0) {
Log.w("AdRevenue", "Invalid revenue: " + revenue);
return;
}
// Validate currency
if (currency == null || currency.isEmpty()) {
Log.w("AdRevenue", "Invalid currency: " + currency);
return;
}
// Create ad revenue data object
SingularAdData data = new SingularAdData(
adPlatform,
currency,
revenue
);
// Send to Singular
Singular.adRevenue(data);
Log.d("AdRevenue", "Revenue sent: " + revenue + " " + currency +
" from " + adPlatform);
}
// Example usage
reportAdRevenue("MyMediationPlatform", "USD", 0.05);
추가 속성으로 광고 데이터 보강하기
SingularAdData 는 임프레션 단위 메타데이터를 위한 체이닝 가능한 세터를 제공합니다. 미디에이션 플랫폼에서 제공하는 경우, 이러한 메서드를 사용하여 플레이스먼트, 광고 단위, 미디에이션 그룹화, 정확성 세부 정보로 이벤트를 보강하세요. 이 메서드로 설정된 속성은 Singular 리포팅과 다운스트림 포스트백에 노출됩니다.
| 메서드 | 설명 |
|---|---|
withNetworkName(String networkName)
|
네트워크 이름을 재정의합니다(기본값은 생성자에 전달된 광고 플랫폼). 미디에이션의 기반 네트워크가 미디에이션 플랫폼과 다를 때 사용합니다. |
withAdType(String adType)
|
광고 포맷 (예:
"Rewarded"
,
"Interstitial"
,
"Banner"
).
|
withAdGroupType(String adGroupType)
|
미디에이션 플랫폼에서 정의된 광고 그룹 분류. |
withImpressionId(String impressionId)
|
임프레션의 고유 식별자로, 중복 제거 및 대조에 사용됩니다. |
withAdPlacementName(String adPlacementName)
|
사람이 읽을 수 있는 플레이스먼트 이름 (예:
"level_complete"
).
|
withAdUnitId(String adUnitId)
|
플랫폼별 광고 단위 식별자 (예: AdMob의
"ca-app-pub-..."
).
|
withAdUnitName(String adUnitName)
|
사람이 읽을 수 있는 광고 단위 이름. |
withAdGroupId(String adGroupId)
|
미디에이션 플랫폼의 광고 그룹 식별자. |
withAdGroupName(String adGroupName)
|
사람이 읽을 수 있는 광고 그룹 이름. |
withAdGroupPriority(String adGroupPriority)
|
미디에이션 워터폴에서 광고 그룹에 할당된 우선순위. |
withPrecision(String precision)
|
플랫폼에서 지원하는 경우의 매출 정확성 지표 (예:
"publisher_provided"
,
"estimated"
,
"exact"
).
|
withPlacementId(String placementId)
|
플랫폼별 플레이스먼트 식별자. |
withLimitDataSharing(boolean shouldLimitDataSharing)
|
이 Singular 광고 이벤트에 대해 글로벌 데이터 공유 제한을 재정의합니다. 예를 들어 사용자가 맞춤형 광고를 옵트아웃한 경우에 사용합니다. |
Kotlin 사용 예시:
val data = SingularAdData("AdMob", "USD", 0.05)
.withAdUnitId("ca-app-pub-123456789/1234567890")
.withAdType("Rewarded")
.withAdPlacementName("level_complete")
.withImpressionId("imp_abc123")
.withPrecision("publisher_provided")
Singular.adRevenue(data)
Java 사용 예시:
SingularAdData data = new SingularAdData("AdMob", "USD", 0.05)
.withAdUnitId("ca-app-pub-123456789/1234567890")
.withAdType("Rewarded")
.withAdPlacementName("level_complete")
.withImpressionId("imp_abc123")
.withPrecision("publisher_provided");
Singular.adRevenue(data);
테스트 및 검증
매출 리포팅 검증
Singular로 데이터가 올바르게 전달되는지 확인하기 위해 광고 매출 구현을 테스트하세요.
- 로그 확인: 매출 콜백 로그가 올바른 값과 통화로 표시되는지 확인하세요.
- 광고 테스트: 테스트 광고를 로드하고 표시하여 매출 이벤트를 트리거하세요.
- 대시보드 검증: 24시간 이내에 Singular 대시보드에 매출이 나타나는지 확인하세요.
- 데이터 정확성: 매출 금액이 미디에이션 플랫폼 리포트와 일치하는지 검증하세요.
트러블슈팅: Singular에 매출이 표시되지 않는 경우 다음을 확인하세요:
- Singular 계정에서 광고 매출 어트리뷰션이 활성화되어 있는지
- 매출 값이 0보다 큰지
- 통화 코드가 유효한 ISO 4217 코드인지
- 플랫폼 이름이 Singular의 예상 형식과 일치하는지