개요
이 가이드는 공유 S2S 메커니즘 위에 더해지는 모바일 전용 어트리뷰션 신호, 즉 Apple Search Ads, Google Play 및 Meta 설치 리퍼러, 앱 삭제 추적, iOS 설치 영수증을 다룹니다. 세션과 이벤트를 server-to-server 방식으로 리포트하는 iOS 및 Android 앱 연동에 적용됩니다.
이 기능들은 공유 S2S 요청 흐름이 이미 구축되어 있다고 가정합니다. 이 기능들이 기반으로 하는 엔드포인트, 인증, 공통 파라미터에 대해서는 먼저 fundamentals 허브와 두 개의 엔드포인트 레퍼런스를 참고하세요.
시작하기 전에: 이 기능들은 공유 S2S 요청 흐름을 기반으로 합니다. 엔드포인트와 공통 파라미터는 S2S Fundamentals 허브 를 참고하고, SESSION 엔드포인트 레퍼런스 와 EVENT 엔드포인트 레퍼런스 도 함께 확인하세요.
Apple Search Ads 어트리뷰션
iOS Search Ads 연동
Apple Search Ads는 Self-Attributing Network(SAN)로, Apple 프레임워크를 통한 플랫폼별 어트리뷰션 구현이 필요합니다.
프레임워크 지원:
- iOS 14.2 이하: iAd 프레임워크
- iOS 14.3 이상: AdServices 프레임워크 (권장)
이중 구현: iAd가 지원 중단될 때까지 iAd와 AdServices 프레임워크를 모두 구현하세요. Singular는 어트리뷰션과 리포팅에서 iAd 신호보다 AdServices를 우선합니다.
전체 가이드: Apple Search Ads 연동 문서
iAd 프레임워크 구현
iOS 14.2 이하에서 iAd 프레임워크를 통해 Apple Search Ads 어트리뷰션 데이터를 가져와 전송합니다.
1단계: 어트리뷰션 데이터 가져오기
#import <iAd/iAd.h>
Class ADClientClass = NSClassFromString(@"ADClient");
if (ADClientClass) {
id sharedClient = [ADClientClass performSelector:@selector(sharedClient)];
if ([sharedClient respondsToSelector:@selector(requestAttributionDetailsWithBlock:)]) {
[sharedClient requestAttributionDetailsWithBlock:^(NSDictionary *attributionDetails, NSError *error) {
if (attributionDetails && attributionDetails.count > 0) {
// REPORT attributionDetails FROM YOUR APP TO YOUR SERVER
}
}];
}
}
지연 시간 처리: Search Ads를 클릭한 사용자는 앱을 즉시 다운로드하고 열 수 있습니다. 다음 방법으로 어트리뷰션 타이밍 문제를 방지하세요:
- 어트리뷰션 데이터를 가져오기 전에 몇 초의 지연을 설정합니다
- 응답이 FALSE이거나 오류 코드(0, 2, 3)인 경우 재시도 로직을 구현합니다. 2초 후에 재시도하세요
2단계: Singular로 전송
예약된 이름
__iAd_Attribution__
으로
EVENT 엔드포인트
를 통해 이벤트를 리포트하며, 어트리뷰션 JSON을
e
파라미터로 전달합니다.
타이밍 요구사항:
-
iOS 13+: 설치/재설치 후 첫 세션 직후에
__iAd_Attribution__이벤트를 전송하세요. 그렇지 않으면 Apple Search Ads 데이터가 어트리뷰션에 반영되지 않습니다 -
iOS 14+: 어트리뷰션 응답은
특정 조건에서만
사용할 수 있으며, ATT 상태가
ATTrackingManager.AuthorizationStatus.denied인 경우 사용할 수 없습니다
AdServices 프레임워크 구현
iOS 14.3 이상에서 AdServices 프레임워크를 통해 어트리뷰션 토큰을 가져와 전송합니다.
1단계: 어트리뷰션 토큰 가져오기
#import <AdServices/AdServices.h>
NSError *error = nil;
Class AAAttributionClass = NSClassFromString(@"AAAttribution");
if (AAAttributionClass) {
NSString *attributionToken = [AAAttributionClass attributionTokenWithError:&error];
if (!error && attributionToken) {
// Handle attributionToken
}
}
토큰 특성:
- 기기에서 생성됩니다
-
5분간 캐시됩니다—만료 후
attributionToken()을 호출하면 새 토큰이 생성됩니다 - 24시간 동안 유효합니다
2단계: Singular로 전송
토큰을 URL 인코딩하여 모든 설치 및 재설치 후 첫 세션에서
SESSION 엔드포인트
에
attribution_token
파라미터로 추가합니다.
Google Play 설치 리퍼러
Android 설치 어트리뷰션
Google Play 설치 리퍼러는 가장 정확한 Android 설치 어트리뷰션을 제공하며, Play Store에 도달하기 전 사용자 출처에 대한 정보를 포함합니다.
필수 대상:
- 다음에서의 Facebook 데이터 유저 레벨 익스포트
- 다음과의 공유 Data Destinations
- 포스트백 전송
자세한 정보: Google Install Referrer 문서
구현 단계:
- 첫 앱 실행 시 Play Install Referrer API 를 사용하여 설치 리퍼러를 가져옵니다
-
필수 속성이 포함된
install_refJSON 파라미터를 포함하여 SESSION 엔드포인트 를 통해 세션을 리포트합니다
install_ref 속성:
| 파라미터 | 세부 정보 |
|---|---|
referrer
|
Play Install Referrer API의 리퍼러 값(JSON 객체—문자열로 인코딩) |
referrer_source
|
"service"로 지정 |
clickTimestampSeconds
|
API의 클릭 타임스탬프(예: "1550420123") |
installBeginTimestampSeconds
|
API의 설치 시작 시간 |
current_device_time
|
현재 기기 시간(밀리초 단위, 예: "1550420454906") |
Meta 설치 리퍼러
Facebook 어트리뷰션 강화
2025년 6월 18일부로: Meta의 Advanced Mobile Measurement (AMM) 은 Meta 설치 리퍼러 구현의 필요성을 없앱니다. AMM이 활성화된 경우 권장하지 않습니다.
Meta Referrer는 Android 전용 측정 솔루션으로, Google Play 설치 리퍼러와 Meta 설치 리퍼러 기술을 결합하여 앱 설치에 대한 세분화된 유저 레벨 어트리뷰션 데이터를 제공합니다.
자세히 알아보기: Meta Referrer FAQ
구현 단계:
- 첫 앱 실행 시 Meta 문서 에 따라 Meta 설치 리퍼러를 가져옵니다
-
필수 속성이 포함된
meta_refJSON 파라미터를 포함하여 SESSION 엔드포인트 를 통해 세션을 리포트합니다
meta_ref 속성:
| 파라미터 | 세부 정보 |
|---|---|
is_ct
|
Meta 설치 리퍼러의 is_ct(0 또는 1) |
install_referrer
|
Meta 설치 리퍼러의 install_referrer |
actual_timestamp
|
Meta 설치 리퍼러의 actual_timestamp(예: 1693978124) |
앱 삭제 추적
사일런트 푸시 알림 설정
모든 세션 알림과 함께 푸시 토큰을 전송하여 기기의 사일런트 푸시 알림을 사용해 앱 삭제를 추적합니다.
설정 요구사항:
- 플랫폼별 앱 삭제 추적 설정을 따르세요:
-
플랫폼별 토큰을 SESSION 요청에 추가하세요:
-
iOS:
apns_token(APNs 기기 토큰) -
Android:
fcm(FCM 기기 토큰)
-
iOS:
iOS 설치 영수증
영수증 검증
iOS 세션을 리포트할 때 iOS 설치 영수증을
install_receipt
파라미터로 전달합니다.
import Foundation
import StoreKit
class ReceiptManager {
static func getInstallReceipt() -> String? {
if #available(iOS 18.0, *) {
let semaphore = DispatchSemaphore(value: 0)
var result: String?
Task {
do {
let transaction = try await AppTransaction.shared
result = transaction.jwsRepresentation
semaphore.signal()
} catch {
debugPrint("Failed to get app transaction: \(error.localizedDescription)")
semaphore.signal()
}
}
semaphore.wait()
return result
} else {
guard let receiptURL = Bundle.main.appStoreReceiptURL else {
debugPrint("Receipt URL not found")
return nil
}
do {
let receiptData = try Data(contentsOf: receiptURL, options: .uncached)
return receiptData.base64EncodedString(options: [])
} catch {
debugPrint("Failed to read receipt: \(error.localizedDescription)")
return nil
}
}
}
}