SKAdNetwork 4 구현 가이드
SKAdNetwork 활용 사례
SKAdNetwork(SKAN)는 사용자의 개인정보를 보호하면서 iOS 앱 인스톨 광고 캠페인을 측정할 수 있는 Apple의 개인정보 중심 어트리뷰션 프레임워크입니다. 서버 간(S2S) 구현은 서버 간에 직접 SKAdNetwork 데이터를 전송하여 캠페인 성과를 검증하고 추적하는 강력한 방법을 제공함으로써 정확한 어트리뷰션 및 전환 추적을 보장합니다.
이 프레임워크는 어트리뷰션의 모든 중요한 측면을 처리하는 동시에 Apple의 규정된 방법을 통해 사용자 개인 정보를 보호하므로, iOS 14.5 이후 환경에서 활동하는 모바일 마케터에게 필수적인 도구입니다.
주요 포인트
- 사기 방지 기능이 내장된 모든 네트워크의 포스트백에 대한 강력한 검증 및 집계
- 대시보드 구성을 통한 동적 전환 가치 관리
- 강화된 마케팅 파라미터와 세분화된 데이터 인사이트를 통한 향상된 리포팅 기능
- 디코딩된 전환 가치 및 구매 추적을 위한 안전한 파트너 포스트백 시스템
- 강화된 이벤트 및 세션 추적을 통한 클라이언트 측 구현의 포괄적인 검증
- 여러 포스트백 기간(0~2일, 3~7일, 8~35일)에 걸친 자동화된 타임스탬프 관리
- 통화 사양을 통해 광고 구매화 및 정기적인 구매 추적 모두 지원
전제 조건
- SKAN 4.0 FAQ에서 SKAdNetwork 4.0에 대해 자세히 알아보세요.
- SKAdNetwork에 대한 자세한 배경 지식은 SKAdNetwork 3.0 S2S 구현 가이드를 참조하세요.
주요 구성 요소
Singular의 SKAdNetwork 솔루션은 다음과 같은 구성 요소로 이루어져 있습니다:
-
SKAdNetwork를 구현하기 위한 클라이언트 측 코드
전환 값 API 엔드포인트를 사용하는 서버 측 접근 방식을 사용할 수 있습니다. - 모든 네트워크에서 포스트백 유효성 검사 및 집계
-
사기 방지:
- 서명 유효성 검사 및 트랜잭션 ID 중복 제거.
- 서명되지 않은 파라미터(전환 값 및 지오데이터)를 검증하기 위해 네트워크를 통한 보안 설정.
- 전환 가치 관리: Singular의 대시보드에서 동적으로 구성할 수 있는 기능을 제공하여 설치 후 활동을 SKAdNetwork 전환 가치로 인코딩할 수 있습니다.
- 리포팅: 제한된 SKAdNetwork 캠페인 ID를 변환하고 더 많은 마케팅 파라미터와 세분화로 데이터를 보강합니다.
- 파트너 포스트백: 최적화에 중요한 전환 가치를 이벤트 및 구매으로 디코딩하여 SKAdNetwork 포스트백을 전송합니다.
클라이언트 측 SKAdNetwork 구현은 크게 두 부분으로 구성됩니다:
- SKAdNetwork 클라이언트 측 구현: 이 부분은 앱을 SKAdNetwork에 등록하고 SKAdNetwork 전환 가치를 지능적으로 관리하는 데 매우 중요합니다. 즉, 이를 구현하면 SKAdNetwork 어트리뷰션 및 관련 인스톨 후 활동을 기반으로 캠페인을 최적화할 수 있습니다.
- 서버 측 연동 업데이트: 이 부분은 클라이언트 측 구현을 검증하고 문제를 해결하는 데 중요합니다. 세션 및 이벤트 엔드포인트를 통해 Singular로 전송되는 이벤트와 세션을 SKAdNetwork 메타데이터로 보강함으로써, 앱 측에서 구현이 제대로 이루어졌는지 확인할 수 있습니다.
시작하기
SKAdNetwork 클라이언트 측 구현
Singular는 SKAdNetwork 등록 및 전환 값 관리를 지원하는 SKAdNetwork 인터페이스 코드 스니펫을 제공합니다. 이 코드 샘플은 다음과 같은 부분을 담당합니다:
- SKAdNetwork 지원 및 등록
- 전환 가치 관리:
- 이 코드는 Singular의 엔드포인트와 동기식으로 통신하여 설정된 전환 모델에 따라 다음 전환 값을 수신합니다. 이벤트/세션/구매을 보고하고, 이에 대한 응답으로 다음 전환 값을 가져오는데, 이는 Singular의 대시보드에서 측정하도록 구성된 설치 후 활동을 나타내는 인코딩된 숫자입니다.
- 이 코드는 또한 측정 기간별로 SKAdnetwork 메타데이터를 수집합니다. 이 메타데이터는 유효성 검사와 다음 전환 값 계산에 모두 사용됩니다:
- 기본 SKAdNetwork 프레임워크에 대한 첫 번째 호출 타임스탬프
- 기본 SKAdNetwork 프레임워크에 대한 마지막 호출 타임스탬프
- 마지막으로 업데이트된 포스트백 값(거칠게 및 세밀하게 모두)
- 디바이스에서 생성된 총 구매 및 총 애드몬 구매
연동 흐름
위의 다이어그램은 S2S 고객에 대한 SKAdNetwork의 흐름을 보여줍니다:
- 첫째, 앱의 코드는 앱에서 발생하는 이벤트/세션/구매 이벤트에 따라 Singular 서버(SKAdNetwork 전용 엔드포인트를 통해)와 통신하여 최신 전환 값을 동기적으로 가져오고, 이 값으로 SKAdNetwork 프레임워크를 업데이트합니다.
- 둘째, 앱은 기존 이벤트와 세션을 SKAdNetwork 데이터로 보강하여 나중에 유효성 검사에 사용합니다.
- 앱이 새로운 전환 값으로 SKAdNetwork 프레임워크 업데이트를 완료하고 SKAdNetwork 타이머가 만료되면, SKAdNetwork 포스트백이 네트워크에 전송됩니다.
- 네트워크는 보안 설정 또는 일반 설정을 통해 이를 Singular로 전달합니다.
- Singular는 포스트백을 처리합니다:
- 서명 검증
- 구성된 전환 모델을 기반으로 전환 값을 디코딩합니다.
- 네트워크 정보로 포스트백을 보강합니다. 데이터는 파트너와의 연동을 통해 SKAdNetwork 및 네트워크 캠페인 ID에 가입하여 수집합니다.
- 디코딩된 포스트백을 BI 및 파트너에게 전송합니다.
중요한 점은 인스톨 및 디코딩된 이벤트를 포함한 SKAdNetwork 정보는 기존 데이터 세트와 혼용되지 않도록 다른 보고서/API/ETL 테이블 및 포스트백을 통해 액세스할 수 있다는 것입니다. 이는 특히 다음 몇 주 동안 기존 캠페인 활동과 SKAdNetwork를 나란히 측정하고 테스트할 수 있도록 하는 데 중요합니다.
SKAdNetwork 인터페이스
이 헤더 파일은 iOS 앱에서 어트리뷰션 추적, 전환 가치 업데이트, 구매 관리를 위한 방법을 제공하는 SKAdNetwork(SKAN) 연동을 위한 퍼블릭 인터페이스를 정의합니다.
어트리뷰션 등록
앱이 처음 실행될 때 SKAN 어트리뷰션 추적을 초기화하여 초기 전환 값을 0으로 설정하고 기준 타임스탬프를 설정합니다. 기본 Apple API 메서드는 디바이스에 해당 앱에 대한 어트리뷰션 데이터가 있는 경우 알림을 생성합니다.
+ (void)registerAppForAdNetworkAttribution;
전환 가치 관리
- 전환 값은 아래 메소드를 통해 캡처된 디바이스의 설치 후 활동과 사용자가 동적으로 구성할 수 있는 선택한 전환 모델을 기반으로 계산됩니다.
- 이 섹션의 메서드는 선택한 전환 모델과 보고된 설치 후 활동에 따라 Singular의 엔드포인트에서 다음 전환 값을 가져옵니다(위 문서 참조).
- 아래 방법은 다음과 같은 인스톨 후 활동을 기반으로 전환 값을 업데이트합니다:
- 세션: SKAdNetwork를 사용한 사용자의 리텐션 측정에 중요합니다.
- 전환 이벤트: SKAdNetwork를 통한 인스톨 후 전환 이벤트 측정에 중요합니다.
- 구매 이벤트: SKAdNetwork를 통한 구매 측정에 필수적입니다.
세션 추적
리텐션 및 코호트 분석을 위한 세션 기반 추적과 업데이트 후 액션을 위한 완료 핸들러(선택 사항)를 관리합니다.
+ (void)updateConversionValuesAsync:(void(^)(NSNumber *, NSNumber *, BOOL, NSError *))handler;
이벤트 트래킹
Singular로 데이터를 전송하기 전에 전환 이벤트 추적을 처리하여 이벤트 컨텍스트에 따라 전환 값을 업데이트합니다.
+ (void)updateConversionValuesAsync:(NSString *)eventName
withCompletionHandler:(void(^)(NSNumber *, NSNumber *, BOOL, NSError *))handler;
구매 관리
구매 이벤트를 추적하여 광고 구매화와 일반 구매에 대한 별도의 합계를 유지하며, 전환 값 업데이트 전에 호출이 필요합니다.
+ (void)updateRevenue:(double)amount
andCurrency:(NSString *)currency
isAdMonetization:(BOOL)admon;
데이터 검색
다음을 포함한 포괄적인 SKAN 데이터를 반환합니다:
- 현재 및 이전의 세분화된 전환 값
- 다양한 포스트백 윈도우에 걸친 대략적인 값
- 윈도우 잠금 타임스탬프
- 통화별 구매 추적
- 광고 구매화 및 일반 구매에 대한 별도 추적
+ (NSDictionary *)getSkanDetails;
구현 참고 사항
- 메서드는 메인 스레드 차단을 방지하기 위해 비동기 패턴을 사용합니다.
- 구매 추적은 전환 가치 업데이트보다 선행되어야 합니다.
- 세분화된(0-63) 전환 값과 거친(낮음/중간/높음) 전환 값 모두 지원
- 다양한 포스트백 기간에 대해 별도의 추적 유지
- 완료 핸들러를 통해 포괄적인 오류 처리 구현
SKANSnippet.h 인터페이스 코드
//SKANSnippet.h
#import <Foundation/Foundation.h>
@interface SKANSnippet : NSObject
// Register for SKAdNetwork attribution.
// You should call this method as soon as possible once the app is launched for the first time.
// This function sets the conversion value to be 0 and updates the timestamp for additional processing.
+ (void)registerAppForAdNetworkAttribution;
// To track retention and cohorts you need to call this method for each app open.
// It reports the session details and updates the conversion value due to this session if needed.
// The callback passed to the method is optional, you can use it to run code once the conversion value is updated.
+ (void)updateConversionValuesAsync:(void(^)(NSNumber *, NSNumber *, BOOL, NSError *))handler;
// To track conversion events with SKAdNetwork you need to call this method after each event and before this event is sent to Singular.
// It reports the event details and updates the conversion value due to this event if needed.
// The callback passed to the method is optional, you can use it to run code once the conversion value is updated.
+ (void)updateConversionValuesAsync:(NSString *)eventName withCompletionHandler:(void(^)(NSNumber *, NSNumber *, BOOL, NSError *))handler;
// To track revenue with SKAdNetwork you need to call this method before every revenue event.
// It will update the total revenue, so when you call 'updateConversionValuesAsync', the new conversion value will be determined according to the total amout of revenue.
// Note:
// 1. Call this method before calling 'updateConversionValuesAsync' to make sure that revenue is updated.
// 2. In case of retrying an event, avoid calling this method so the same revenue will not be counted twice.
+ (void)updateRevenue:(double)amount andCurrency:(NSString *)currency isAdMonetization:(BOOL)admon;
// Gets current fine, coarse, window locked values. saved in the dictionary under "fineValue", "coarseValue", "windowLock".
// In addition, contains all other relevant values for SKAN purposes.
// e.g.
// {
// "skan_current_conversion_value": 3,
// "prev_fine_value": 2,
// "skan_first_call_to_skadnetwork_timestamp": 167890942,
// "skan_last_call_to_skadnetwork_timestamp": 167831134,
// "skan_total_revenue_by_currency": { "USD": 1.2 },
// "skan_total_admon_revenue_by_currency": { "USD": 0.8 },
// "p0_coarse": 0,
// "p1_coarse": 1,
// "p2_coarse": nil,
// "p0_window_lock": 167890942,
// "p1_window_lock": nil,
// "p2_window_lock": nil,
// "p0_prev_coarse_value": 0,
// "p1_prev_coarse_value": 0,
// "p2_prev_coarse_value": nil,
// "p0_total_iap_revenue": nil,
// "p1_total_iap_revenue": nil,
// "p2_total_iap_revenue": nil,
// "p0_total_admon_revenue": nil,
// "p1_total_admon_revenue": nil,
// "p2_total_admon_revenue": nil
// }
+ (NSDictionary *)getSkanDetails;
@end
SKAdNetwork 인터페이스 구현
이 코드는 여러 포스트백 윈도우에 걸쳐 어트리뷰션 추적, 전환 값, 구매 보고를 관리하는 iOS 앱용 Apple의 SKAdNetwork(SKAN) 인터페이스를 구현합니다.
상수 및 구성
이 구현은 사용자 활동과 전환을 추적하기 위한 세 가지 포스트백 윈도우를 정의합니다.
static NSInteger firstSkan4WindowInSec = 3600 * 24 * 2; // 48 hours
static NSInteger secondSkan4WindowInSec = 3600 * 24 * 7; // 7 days
static NSInteger thirdSkan4WindowInSec = 3600 * 24 * 35; // 35 days
주요 기능
- 어트리뷰션 등록:초기 앱 어트리뷰션 설정 및 최초 전환 가치 추적을 처리합니다.
- 전환 가치 관리: 여러 포스트백 윈도우에서 전환 가치를 업데이트하고 추적합니다.
- 구매 추적: 광고 구매화 및 정기적인 구매 이벤트에 대해 별도의 추적을 유지합니다.
- 데이터 지속성: NSUserDefaults를 사용하여 앱 세션 전반에 걸쳐 SKAN 관련 데이터를 저장합니다.
- 스레드 안전: 네트워크 호출 중 스레드 안전 작업을 위해 NSLock을 구현합니다.
데이터 저장 구조
- 세분화된 변환 값(0-63)
- 거친 값(낮음/중간/높음)
- 통화별 구매 추적
- 포스트백 기간에 대한 타임스탬프 관리
- 세분화 및 거친 전환 모두에 대한 이전 값 추적
개인정보 보호 고려 사항
- iOS 15.4+ 및 iOS 16.1+ 특정 기능 구현
- Apple의 개인정보 보호 가이드라인에 따라 포스트백 전환 가치 업데이트를 처리합니다.
- 정확한 어트리뷰션을 보장하기 위해 다양한 구매 유형에 대해 별도의 트래킹을 유지합니다.
기술 노트
- 네트워크 호출 및 값 업데이트에 비동기 작업 사용
- 전환 값에 대한 오류 처리 및 유효성 검사 구현
- 기존 및 거친 전환 가치 추적 모두 지원
- 기간과 요구사항이 다른 여러 포스트백 윈도우 관리
S2S 연동 업데이트(권장)
다음 SKAdNetwork 메타데이터로 S2S 연동을 업데이트하세요.
이 메타데이터는 세션 알림 엔드포인트와 이벤트 알림 엔드포인트를 통해 Singular에 보고되는 모든 세션과 모든 이벤트에 대해 전달되어야 합니다. 이 데이터는 SKAdNetwork 구현의 유효성 검사에 사용됩니다.
메타데이터 구조
전환 값
데이터 검색 메서드를 사용하여 메타데이터의 사전을 추출하고 서버로 전달하여 세션 및 이벤트 엔드포인트 API 요청에 쿼리 파라미터로 추가합니다.
NSDictionary *values = [SKANSnippet getSkanDetails];
SKANSnippet.m 구현 코드
// SKANSnippet.m
#import "SKANSnippet.h"
#import <StoreKit/SKAdNetwork.h>
#import <UIKit/UIKit.h>
#define SESSION_EVENT_NAME @"__SESSION__"
#define SINGULAR_API_URL @"https://sdk-api-v1.singular.net/api/v2/conversion_value"
// SKAN Keys for NSUserDefaults persistency and requests
#define CONVERSION_VALUE_KEY @"skan_current_conversion_value"
#define FIRST_SKAN_CALL_TIMESTAMP @"skan_first_call_to_skadnetwork_timestamp"
#define LAST_SKAN_CALL_TIMESTAMP @"skan_last_call_to_skadnetwork_timestamp"
#define TOTAL_REVENUE_BY_CURRENCY @"skan_total_revenue_by_currency"
#define TOTAL_ADMON_REVENUE_BY_CURRNECY @"skan_total_admon_revenue_by_currency"
#define SKAN_UPDATED_CONVERSION_VALUE @"conversion_value"
#define SKAN_UPDATED_COARSE_VALUE @"skan_updated_coarse_value"
#define SKAN_UPDATED_LOCK_WINDOW_VALUE @"skan_updated_lock_window_value"
#define P0_COARSE @"p0_coarse"
#define P1_COARSE @"p1_coarse"
#define P2_COARSE @"p2_coarse"
#define P0_WINDOW_LOCK_TS @"p0_window_lock"
#define P1_WINDOW_LOCK_TS @"p1_window_lock"
#define P2_WINDOW_LOCK_TS @"p2_window_lock"
#define P0_PREV_FINE_VALUE @"prev_fine_value"
#define P0_PREV_COARSE_VALUE @"p0_prev_coarse_value"
#define P1_PREV_COARSE_VALUE @"p1_prev_coarse_value"
#define P2_PREV_COARSE_VALUE @"p2_prev_coarse_value"
#define TOTAL_REVENUE_P0 @"p0_total_iap_revenue"
#define TOTAL_REVENUE_P1 @"p1_total_iap_revenue"
#define TOTAL_REVENUE_P2 @"p2_total_iap_revenue"
#define TOTAL_ADMON_REVENUE_P0 @"p0_total_admon_revenue"
#define TOTAL_ADMON_REVENUE_P1 @"p1_total_admon_revenue"
#define TOTAL_ADMON_REVENUE_P2 @"p2_total_admon_revenue"
@implementation SKANSnippet
static NSInteger firstSkan4WindowInSec = 3600 * 24 * 2; //48 hours in sec
static NSInteger secondSkan4WindowInSec = 3600 * 24 * 7;
static NSInteger thirdSkan4WindowInSec = 3600 * 24 * 35;
static NSLock *lockObject;
+ (void)registerAppForAdNetworkAttribution {
if ([SKANSnippet getFirstSkanCallTimestamp] != 0) {
return;
}
if (@available(iOS 15.4, *)) {
[SKAdNetwork updatePostbackConversionValue:0 completionHandler:nil];
[SKANSnippet setFirstSkanCallTimestamp];
[SKANSnippet setLastSkanCallTimestamp];
[SKANSnippet valuesHasBeenUpdated:@(0) coarseValue:nil lockWindow:NO];
}
}
+ (void)updateConversionValuesAsync:(void(^)(NSNumber *, NSNumber *, BOOL, NSError *))handler {
[SKANSnippet updateConversionValuesAsync:SESSION_EVENT_NAME withCompletionHandler:handler];
}
+ (void)updateConversionValuesAsync:(NSString *)eventName withCompletionHandler:(void(^)(NSNumber *, NSNumber *, BOOL, NSError *))handler {
if ([SKANSnippet isSkanWindowOver]) {
return;
}
[SKANSnippet getConversionValueFromServer:eventName withCompletionHandler:handler];
}
+ (void)updateRevenue:(double)amount andCurrency:(NSString *)currency isAdMonetization:(BOOL)admon {
// Update total revenues
if (amount == 0 || !currency ) {
return;
}
[SKANSnippet addToTotalRevenue:@(amount) withCurrency:currency isAdmon:admon];
}
+ (NSDictionary *)getSkanDetails {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *res = [NSMutableDictionary dictionary];
//current fine
[res setValue:[[userDefaults valueForKey:CONVERSION_VALUE_KEY] stringValue] forKey:CONVERSION_VALUE_KEY];
//prev fine
[res setValue:[[userDefaults valueForKey:P0_PREV_FINE_VALUE] stringValue] forKey:P0_PREV_FINE_VALUE];
//current coarse
[res setValue:[[userDefaults valueForKey:P0_COARSE] stringValue] forKey:P0_COARSE];
[res setValue:[[userDefaults valueForKey:P1_COARSE] stringValue] forKey:P1_COARSE];
[res setValue:[[userDefaults valueForKey:P2_COARSE] stringValue] forKey:P2_COARSE];
//prev coarse
[res setValue:[[userDefaults valueForKey:P0_PREV_COARSE_VALUE] stringValue] forKey:P0_PREV_COARSE_VALUE];
[res setValue:[[userDefaults valueForKey:P1_PREV_COARSE_VALUE] stringValue] forKey:P1_PREV_COARSE_VALUE];
[res setValue:[[userDefaults valueForKey:P2_PREV_COARSE_VALUE] stringValue] forKey:P2_PREV_COARSE_VALUE];
//lock windows ts
[res setValue:[[userDefaults valueForKey:P0_WINDOW_LOCK_TS] stringValue] forKey:P0_WINDOW_LOCK_TS];
[res setValue:[[userDefaults valueForKey:P1_WINDOW_LOCK_TS] stringValue] forKey:P1_WINDOW_LOCK_TS];
[res setValue:[[userDefaults valueForKey:P2_WINDOW_LOCK_TS] stringValue] forKey:P2_WINDOW_LOCK_TS];
//total revenues
[res setValue:[userDefaults valueForKey:TOTAL_REVENUE_BY_CURRENCY] forKey:TOTAL_REVENUE_BY_CURRENCY];
[res setValue:[userDefaults valueForKey:TOTAL_ADMON_REVENUE_BY_CURRNECY] forKey:TOTAL_ADMON_REVENUE_BY_CURRNECY];
//revenue per window
[res setValue:[userDefaults valueForKey:TOTAL_REVENUE_P0] forKey:TOTAL_REVENUE_P0];
[res setValue:[userDefaults valueForKey:TOTAL_REVENUE_P1] forKey:TOTAL_REVENUE_P1];
[res setValue:[userDefaults valueForKey:TOTAL_REVENUE_P2] forKey:TOTAL_REVENUE_P2];
[res setValue:[userDefaults valueForKey:TOTAL_ADMON_REVENUE_P0] forKey:TOTAL_ADMON_REVENUE_P0];
[res setValue:[userDefaults valueForKey:TOTAL_ADMON_REVENUE_P1] forKey:TOTAL_ADMON_REVENUE_P1];
[res setValue:[userDefaults valueForKey:TOTAL_ADMON_REVENUE_P2] forKey:TOTAL_ADMON_REVENUE_P2];
//skan TS
[res setValue:[[userDefaults valueForKey:LAST_SKAN_CALL_TIMESTAMP] stringValue] forKey:LAST_SKAN_CALL_TIMESTAMP];
[res setValue:[[userDefaults valueForKey:FIRST_SKAN_CALL_TIMESTAMP] stringValue] forKey:FIRST_SKAN_CALL_TIMESTAMP];
return res;
}
#pragma mark - internal
+ (BOOL)validateValues:(NSNumber *)conversionValue coarse:(NSNumber *)coarseValue{
if ([conversionValue intValue] < 0 || 63 < [conversionValue intValue]) {
return NO;
}
if (coarseValue) {
if ([coarseValue intValue] > 2 || [coarseValue intValue] < 0) {
return NO;
}
}
return YES;
}
+ (NSURLComponents *)prepareQueryParams:(NSString *)bundleIdentifier eventName:(NSString *)eventName {
NSURLComponents *components = [NSURLComponents componentsWithString:SINGULAR_API_URL];
NSString *API_KEY = @"YOUR API KEY";
NSString *APP_VERSION = @"YOUR APP VERSION";
NSString *IDFV = @"IDFV";
NSString *IDFA = @"IDFA";
NSMutableArray *queryItems = [@[
[NSURLQueryItem queryItemWithName:@"a" value:API_KEY],
[NSURLQueryItem queryItemWithName:@"v" value:[[UIDevice currentDevice] systemVersion]],
[NSURLQueryItem queryItemWithName:@"i" value:bundleIdentifier],
[NSURLQueryItem queryItemWithName:@"app_v" value:APP_VERSION],
[NSURLQueryItem queryItemWithName:@"n" value:eventName],
[NSURLQueryItem queryItemWithName:@"p" value:@"iOS"],
[NSURLQueryItem queryItemWithName:@"idfv" value:IDFV],
[NSURLQueryItem queryItemWithName:@"idfa" value:IDFA]
] mutableCopy];
NSDictionary *skanValues = [SKANSnippet getSkanDetails];
[skanValues enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[NSDictionary class]]) {
[queryItems addObject:[NSURLQueryItem queryItemWithName:key value:[SKANSnippet dictionaryToJsonString:obj]]];
} else {
[queryItems addObject:[NSURLQueryItem queryItemWithName:key value:obj]];
}
}];
components.queryItems = queryItems;
return components;
}
+ (void)getConversionValueFromServer:(NSString*)eventName withCompletionHandler:(void(^)(NSNumber *, NSNumber *, BOOL, NSError*))handler {
if (!lockObject) {
lockObject = [NSLock new];
}
@try {
// Making the lock async so it will not freeze the calling thread
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
[lockObject lock];
NSString *bundleIdentifier = @"YOUR BUNDLE IDENTIFIER";
NSURLComponents *components = [SKANSnippet prepareQueryParams:bundleIdentifier eventName:eventName];
[[[NSURLSession sharedSession] dataTaskWithURL:components.URL
completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
[lockObject unlock];
if (handler) {
handler(nil, nil, NO, error);
}
return;
}
NSDictionary *parsedResponse = [SKANSnippet jsonDataToDictionary:data];
if (!parsedResponse) {
[lockObject unlock];
if (handler) {
handler(nil,nil, NO, [NSError errorWithDomain:bundleIdentifier
code:0
userInfo:@{NSLocalizedDescriptionKey:@"Failed parsing server response"}]);
}
return;
}
NSNumber *conversionValue = [parsedResponse objectForKey:SKAN_UPDATED_CONVERSION_VALUE];
NSNumber *coarseValue = [parsedResponse objectForKey:SKAN_UPDATED_COARSE_VALUE];
BOOL lockWindow = [[parsedResponse objectForKey:SKAN_UPDATED_LOCK_WINDOW_VALUE] boolValue];
if (!conversionValue) {
[lockObject unlock];
NSString *status = [parsedResponse objectForKey:@"status"];
if (!status || ![status isEqualToString:@"ok"]) {
if (handler) {
NSString *reason = [parsedResponse objectForKey:@"reason"];
if (!reason) {
reason = @"Got error from server";
}
handler(nil, nil, NO, [NSError errorWithDomain:bundleIdentifier
code:0
userInfo:@{NSLocalizedDescriptionKey:reason}]);
}
}
return;
}
if(![SKANSnippet validateValues:conversionValue coarse:coarseValue]) {
if (handler) {
handler(nil,nil, NO, [NSError errorWithDomain:bundleIdentifier
code:0
userInfo:@{NSLocalizedDescriptionKey:@"Illegal values recieved"}]);
}
return;
}
if (![SKANSnippet getFirstSkanCallTimestamp]) {
[SKANSnippet setFirstSkanCallTimestamp];
}
[SKANSnippet setConversionValues:conversionValue coarseValue:coarseValue lockWindow:lockWindow handler:handler];
[lockObject unlock];
}] resume];
});
} @catch (id exception) {
NSLog(@"%@", exception);
}
}
+ (void)setFirstSkanCallTimestamp {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp] forKey:FIRST_SKAN_CALL_TIMESTAMP];
[userDefaults synchronize];
}
+ (void)setLastSkanCallTimestamp {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setInteger:[SKANSnippet getCurrentUnixTimestamp] forKey:LAST_SKAN_CALL_TIMESTAMP];
[userDefaults synchronize];
}
+ (NSString*)dictionaryToJsonString:(NSDictionary*)dictionary {
if (!dictionary || [dictionary count] == 0){
return @"{}";
}
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dictionary
options:0
error:&error];
if (error || !jsonData) {
return @"{}";
}
return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}
+ (NSInteger)getFirstSkanCallTimestamp {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
return [userDefaults integerForKey:FIRST_SKAN_CALL_TIMESTAMP];
}
+ (NSInteger)getLastSkanCallTimestamp {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
return [userDefaults integerForKey:LAST_SKAN_CALL_TIMESTAMP];
}
+ (NSInteger)getCurrentUnixTimestamp {
return [[NSDate date] timeIntervalSince1970];
}
+ (void)setConversionValues:(NSNumber *)conversionValue coarseValue:(NSNumber *)coarse lockWindow:(BOOL)lockWindow handler:(void(^)(NSNumber *, NSNumber *, BOOL, NSError*))handler {
@try {
__block void(^skanResultHandler)(NSError * _Nullable error) = ^(NSError * _Nullable error) {
if (handler) {
if (error) {
handler(nil, nil, NO, error);
} else {
handler(conversionValue, coarse, lockWindow, nil);
}
}
[SKANSnippet valuesHasBeenUpdated:conversionValue coarseValue:coarse lockWindow:lockWindow];
};
if (@available(iOS 16.1, *)) {
[SKAdNetwork updatePostbackConversionValue:[conversionValue integerValue] coarseValue:[SKANSnippet resolveCoarseValueFrom:coarse] lockWindow:lockWindow completionHandler:^(NSError * _Nullable error) {
skanResultHandler(error);
}];
} else {
if (@available(iOS 15.4, *)) {
[SKAdNetwork updatePostbackConversionValue:[conversionValue integerValue] completionHandler:^(NSError * _Nullable error) {
skanResultHandler(error);
}];
}
}
} @catch (id exception) {
NSLog(@"%@", exception);
}
}
+ (NSNumber *)getConversionValue {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (![userDefaults objectForKey:CONVERSION_VALUE_KEY]) {
return @(0);
}
return @([userDefaults integerForKey:CONVERSION_VALUE_KEY]);
}
+ (NSDictionary*)jsonDataToDictionary:(NSData*)jsonData {
if (!jsonData) {
return nil;
}
NSError *error;
NSDictionary *parsedData = [NSJSONSerialization JSONObjectWithData:jsonData
options:kNilOptions error:&error];
if (error || !parsedData) {
return nil;
}
return parsedData;
}
+ (NSInteger)getCurrentSkanWindow {
NSInteger timeDiff = [SKANSnippet getCurrentUnixTimestamp] - [SKANSnippet getFirstSkanCallTimestamp];
if (timeDiff < firstSkan4WindowInSec) { return 0; }
if (timeDiff < secondSkan4WindowInSec) { return 1; }
if (timeDiff < thirdSkan4WindowInSec) { return 2; }
return -1;
}
// persist updated conversion values based on the active skan window.
+ (void)valuesHasBeenUpdated:(NSNumber *)fineValue coarseValue:(NSNumber *)coarseValue lockWindow:(BOOL)lockWindow {
NSNumber *currentPersistedFineValue;
NSNumber *currentPersistedCoarseValue;
NSInteger window = [SKANSnippet getCurrentSkanWindow];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
switch (window) {
case 0:
currentPersistedFineValue = [userDefaults objectForKey:CONVERSION_VALUE_KEY];
currentPersistedCoarseValue = [userDefaults objectForKey:P0_COARSE];
[userDefaults setValue:fineValue forKey:CONVERSION_VALUE_KEY];
[userDefaults setValue:currentPersistedFineValue forKey:P0_PREV_FINE_VALUE];
[userDefaults setValue:coarseValue forKey:P0_COARSE];
[userDefaults setValue:currentPersistedCoarseValue forKey:P0_PREV_COARSE_VALUE];
if (lockWindow) {
[userDefaults setObject:@([SKANSnippet getCurrentUnixTimestamp]) forKey:P0_WINDOW_LOCK_TS];
}
break;
case 1:
currentPersistedCoarseValue = [userDefaults objectForKey:P1_COARSE];
[userDefaults setValue:coarseValue forKey:P1_COARSE];
[userDefaults setValue:currentPersistedCoarseValue forKey:P1_PREV_COARSE_VALUE];
if (lockWindow) {
[userDefaults setObject:@([SKANSnippet getCurrentUnixTimestamp]) forKey:P1_WINDOW_LOCK_TS];
}
break;
case 2:
currentPersistedCoarseValue = [userDefaults objectForKey:P2_COARSE];
[userDefaults setValue:coarseValue forKey:P2_COARSE];
[userDefaults setValue:currentPersistedCoarseValue forKey:P2_PREV_COARSE_VALUE];
if (lockWindow) {
[userDefaults setValue:@([SKANSnippet getCurrentUnixTimestamp]) forKey:P2_WINDOW_LOCK_TS];
}
break;
}
[SKANSnippet setLastSkanCallTimestamp];
}
+ (BOOL)isSkanWindowOver {
NSInteger timeDiff = [SKANSnippet getCurrentUnixTimestamp] - [SKANSnippet getFirstSkanCallTimestamp];
return thirdSkan4WindowInSec <= timeDiff;
}
// Revenues are being accumulated and saved by Ad monetization and non ad monetization events, total sum and break down by skan windows.
+ (void)addToTotalRevenue:(NSNumber *)newRevenue withCurrency:(NSString *)currency isAdmon:(BOOL)isAdmon {
NSString *key = isAdmon ? TOTAL_ADMON_REVENUE_BY_CURRNECY : TOTAL_REVENUE_BY_CURRENCY;
[SKANSnippet addToTotalRevenue:newRevenue withCurrency:currency forKey:key];
NSInteger window = [SKANSnippet getCurrentSkanWindow];
switch (window) {
case 0:
key = isAdmon ? TOTAL_ADMON_REVENUE_P0 : TOTAL_REVENUE_P0 ;
break;
case 1:
key = isAdmon ? TOTAL_ADMON_REVENUE_P1 : TOTAL_REVENUE_P1 ;
break;
case 2:
key = isAdmon ? TOTAL_ADMON_REVENUE_P2 : TOTAL_REVENUE_P2 ;
break;
case -1:
key = nil;
return;
}
[SKANSnippet addToTotalRevenue:newRevenue withCurrency:currency forKey:key];
}
// Coarse value is being sent on requests and responses as an Int and being translated into the system defined coarse value upon API execution.
+ (NSString *)resolveCoarseValueFrom:(NSNumber *)value {
if(@available(iOS 16.1, *)) {
if (!value) {
return nil;
}
switch ([value integerValue]) {
case 0:
return SKAdNetworkCoarseConversionValueLow;
case 1:
return SKAdNetworkCoarseConversionValueMedium;
case 2:
return SKAdNetworkCoarseConversionValueHigh;
default:
return nil;
}
}
return nil;
}
+ (NSDictionary*)getTotalRevenue:(NSString *)revenueKey {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if (![userDefaults objectForKey:revenueKey]){
[userDefaults setObject:[NSDictionary dictionary] forKey:revenueKey];
}
return [userDefaults objectForKey:revenueKey];
}
+ (void)addToTotalRevenue:(NSNumber *)newRevenue withCurrency:(NSString *)currency forKey:(NSString *)revenueKey {
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *revenues = [[SKANSnippet getTotalRevenue:revenueKey] mutableCopy];
NSNumber *currentRevenue = 0;
if ([revenues objectForKey:currency]) {
currentRevenue = [revenues objectForKey:currency];
}
currentRevenue = @([currentRevenue floatValue] + [newRevenue floatValue]);
[revenues setObject:currentRevenue forKey:currency];
[userDefaults setObject:revenues forKey:revenueKey];
[userDefaults synchronize];
}
@end
앱 라이프사이클 흐름 예시
이 코드는 iOS 앱 라이프사이클에서 앱 실행, 세션, 이벤트, 구매 추적을 처리하는 SKAdNetwork(SKAN) 어트리뷰션의 주요 연동 포인트를 보여줍니다.
구현 참고 사항
- 이 코드는 메인 스레드 차단을 방지하기 위해 전환 값 업데이트에 비동기 방식을 사용합니다.
- 모든 SKAN 관련 데이터는 서버로 전송되기 전에 사전 형식으로 수집됩니다.
- 이 구현은 Apple의 개인정보 보호 우선 접근 방식을 따르면서도 필수적인 어트리뷰션 추적을 허용합니다.
- 정확한 재무 보고를 위해 구매 추적에 금전적 가치와 통화 사양이 모두 포함됩니다.
앱 첫 실행
이 코드는 어트리뷰션 추적을 위해 앱을 SKAdNetwork에 등록하고 초기 세션 데이터를 Singular의 엔드포인트로 전송합니다. 이는 어트리뷰션 추적을 설정하기 위해 앱을 처음 실행할 때만 실행됩니다.
이 코드는 어트리뷰션 추적을 설정하기 위해 앱을 처음 실행할 때만 실행됩니다.
[SKANSnippet registerAppForAdNetworkAttribution];
NSDictionary *skanValues = [SKANSnippet getSkanDetails];
[self sendSessionToServer:skanValues] //to Singular launch EP
세션 관리
이 섹션은 각 세션 후 전환 값을 업데이트하고 업데이트된 SKAN 세부 정보를 전송하여 사용자 참여를 추적합니다.
[SKANSnippet updateConversionValuesAsync:handler];
NSDictionary *skanValues = [SKANSnippet getSkanDetails];
[self sendSessionToServer:skanValues]
이벤트 추적
이 코드는 전환 값을 업데이트하고 이벤트 데이터를 Singular의 이벤트 엔드포인트로 전송하여 비구매 이벤트를 처리합니다.
[SKANSnippet updateConversionValuesAsync:@"event_name" withCompletionHandler:handler];
NSDictionary *skanValues = [SKANSnippet getSkanDetails];
[self sendEventToServer:skanValues eventName:@"event_name"]
구매 추적
이 섹션에서는 통화로 구매 금액과 관련 전환 값을 모두 업데이트하여 구매 이벤트를 관리합니다. 그런 다음 데이터는 구매 관련 활동을 추적하기 위해 Singular의 이벤트 엔드포인트로 전송됩니다.
[SKANSnippet updateRevenue:15.53 andCurrency:@"KRW"];
[SKANSnippet updateConversionValuesAsync:@"revenue_event_name" withCompletionHandler:handler];
NSDictionary *skanValues = [SKANSnippet getSkanDetails];
[self sendEventToServer:skanValues eventName:@"revenue_event_name"]
전환 가치 API
SKAdNetwork 전환 가치는 두 가지 방법을 통해 보고할 수 있습니다:
- 클라이언트 측에서 직접 SKAdNetwork 인터페이스 구현 - 위 참조
- 전환 가치 API 엔드포인트를 사용한 서버 측 연동
두 가지 방법 모두 동일한 데이터 흐름과 보고 무결성을 유지하므로, 기술 아키텍처에 가장 적합한 구현 방식을 선택할 수 있습니다. 전환 가치 API 엔드포인트는 클라이언트 측 인터페이스와 동일한 파라미터를 허용하므로 일관된 어트리뷰션 추적을 보장합니다.
내용
전환 값 API 엔드포인트
HTTP 메서드 및 변환 값 엔드포인트
GET https://sdk-api-v1.singular.net/api/v2/conversion_value
필수 파라미터
다음 표에는 서버에서 전환 API를 지원하기 위한 필수 및 선택 매개변수가 나열되어 있습니다. 나열된 모든 매개변수는 쿼리 매개변수입니다.
필수 매개변수 | |
---|---|
API 키 | |
파라미터 | 설명 |
|
a 매개변수는 Singular SDK 키를 지정합니다. 메인 메뉴의 개발자 도구 아래 Singular UI에서 SDK 키를 검색합니다. 참고: 보고 API 키는 데이터가 거부될 수 있으므로 사용하지 마세요. 예시 값:
|
디바이스 식별자 매개변수 | |
파라미터 | 설명 |
|
광고주가 사용자 행동(예: 광고 클릭, 앱 설치)을 추적하고 특정 캠페인에 어트리뷰션하여 정확한 광고 타겟팅 및 캠페인 최적화를 가능하게 하는 IDFA(광고주 식별자) 를 지정하는 매개변수입니다. iOS 14.5부터는 앱이 IDFA에 액세스하기 전에 사용자가 ATT(앱 추적 투명성) 프레임워크를 통해 옵트인해야 합니다. 사용자가 IDFA에 옵트인하지 않으면 IDFA를 사용할 수 없게 되어 추적 기능이 제한됩니다.
예제 값:
|
파라미터 | 설명 |
|
idfv 매개변수는 특정 공급업체 또는 개발자에게만 해당하는 Apple이 장치에 할당하는 고유 식별자인 IDFV(공급업체 식별자)를 지정합니다. 특정 기기에서 동일한 공급업체의 모든 앱에서 일관성을 유지하므로 공급업체는 사용자를 개인적으로 식별하지 않고도 앱 생태계 전반에서 사용자 행동과 상호 작용을 추적할 수 있습니다.
예제 값:
|
장치 매개변수 | |
파라미터 | 설명 |
|
p 매개변수는 앱의 플랫폼을 지정합니다. 이 API는 iOS에서만 사용되므로 이 값은 iOS여야 합니다. 값 예시:
|
매개변수 | 설명 |
|
v 매개변수는 세션 시점의 디바이스 OS 버전을 지정합니다. 예제 값입니다:
|
애플리케이션 매개변수 | |
매개변수 | 설명 |
|
i 매개변수는 앱 식별자를 지정합니다. 이는 iOS 애플리케이션의 번들 ID입니다. (대소문자 구분) 예제 값입니다:
|
파라미터 | 설명 |
|
app_v 매개변수는 애플리케이션 버전을 지정합니다. 예시:
|
이벤트 매개변수 | |
파라미터 | 설명 |
|
n 매개변수는 추적 중인 이벤트의 이름을 지정합니다.
예시 값:
|
전환 값 매개변수 | |
파라미터 | 설명 |
지원되는 플랫폼:
|
이전 세션/이벤트 알림 시점의 최신 SKAdNetwork 전환 값입니다. (0-63) 사이의 정수입니다.
예시 값입니다:
|
파라미터 | 설명 |
지원 플랫폼:
|
이전 세션/이벤트 알림 시점의 postback_sequence 1에 대한 최신 SKAdNetwork 거친 변환 값입니다. 이 값은 (0-2) 사이의 정수입니다.
예시 값입니다:
|
파라미터 | 설명 |
지원 플랫폼:
|
이전 세션/이벤트 알림 시점의 postback_sequence 2에 대한 최신 SKAdNetwork 거친 변환 값입니다. 이 값은 (0-2) 사이의 정수입니다.
예시 값입니다:
|
구매 추적 파라미터 | |
파라미터 | 설명 |
지원 플랫폼:
|
IAP 또는 모든 구매 모델을 사용하는 경우 필수입니다. 광고 구매화 구매을 제외한 기기에서 발생한 IAP 구매의 현재 집계된 총합계입니다.
예시 값입니다:
|
파라미터 | 설명 |
지원 플랫폼:
|
애드몬 또는 모든 구매 전환 모델을 사용하는 경우 필수입니다. 기기에서 발생한 광고 구매 창출 구매의 현재 집계된 총합입니다.
예시 값입니다:
|
타임스탬프 파라미터 | |
파라미터 | 설명 |
지원되는 플랫폼:
|
기본 SKAdNetwork API에 대한 첫 번째 호출의 유닉스 타임스탬프입니다.
예시 값입니다:
|
파라미터 | 설명 |
지원 플랫폼:
|
이 세션 알림이 발생한 시점의 기본 SKAdNetwork API에 대한 최신 호출의 유닉스 타임스탬프입니다.
예시 값입니다:
|
요청 본문
이 메서드를 호출할 때 요청 본문은 제공하지 마세요. 요청은 쿼리 파라미터와 함께 GET 메서드를 사용하여 전송해야 합니다.
요청 예제
다음 코드 샘플은 지원되는 모든 매개변수를 나타내지 않을 수 있습니다. 요청을 구현할 때는 위에 나열된 모든 필수 매개변수를 포함하고 프로덕션 인스턴스에서 데이터를 전송하기 전에 올바른 값이 전달되고 있는지 확인하세요. 개발 및 테스트를 위해 고유한 `i` 매개변수(애플리케이션 식별자)를 사용하는 것이 좋습니다.
PYTHON
import requests
params = {
'a': 'sdk_key_here',
'p': 'iOS',
'i': 'com.singular.app',
'v': '16.1',
'idfa': 'DFC5A647-9043-4699-B2A5-76F03A97064B',
'idfv': '21DB6612-09B3-4ECC-84AC-B353B0AF1334',
'n': '__SESSION__',
'app_v': '1.2.3',
'skan_current_conversion_value': 7,
'p1_coarse': 0,
'p2_coarse': 1,
'skan_total_revenue_by_currency': {"USD":9.99},
'skan_total_admon_revenue_by_currency': {"USD":1.2},
'skan_first_call_to_skadnetwork_timestamp': 1510090877,
'skan_last_call_to_skadnetwork_timestamp': 1510090877
}
response = requests.get('https://sdk-api-v1.singular.net/api/v2/conversion_value', params=params)
print(response.json())
CURL
curl -G 'https://sdk-api-v1.singular.net/api/v2/conversion_value' \
--data-urlencode 'a=sdk_key_here' \
--data-urlencode 'p=iOS' \
--data-urlencode 'i=com.singular.app' \
--data-urlencode 'v=16.1' \
--data-urlencode 'idfa=DFC5A647-9043-4699-B2A5-76F03A97064B' \
--data-urlencode 'idfv=21DB6612-09B3-4ECC-84AC-B353B0AF1334' \
--data-urlencode 'n=__SESSION__' \
--data-urlencode 'app_v=1.2.3' \
--data-urlencode 'skan_current_conversion_value=7' \
--data-urlencode 'p1_coarse=0' \
--data-urlencode 'p2_coarse=1' \
--data-urlencode 'skan_total_revenue_by_currency={"USD":9.99}' \
--data-urlencode 'skan_total_admon_revenue_by_currency={"USD":1.2}' \
--data-urlencode 'skan_first_call_to_skadnetwork_timestamp=1510040127' \
--data-urlencode 'skan_last_call_to_skadnetwork_timestamp=1510090877'
HTTP
GET /api/v2/conversion_value
?a=sdk_key_here
&p=iOS
&i=com.singular.app
&v=16.1
&idfa=DFC5A647-9043-4699-B2A5-76F03A97064B
&idfv=21DB6612-09B3-4ECC-84AC-B353B0AF1334
&n=__SESSION__
&app_v=1.2.3
&skan_current_conversion_value=7
&p1_coarse=0
&p2_coarse=1
&skan_total_revenue_by_currency=%7B%22USD%22%3A9.99%7D
&skan_total_admon_revenue_by_currency=%7B%22USD%22%3A1.2%7D
&skan_first_call_to_skadnetwork_timestamp=1510090877
&skan_last_call_to_skadnetwork_timestamp=1510090877 HTTP/1.1
Host: sdk-api-v1.singular.net
Accept: application/json
JAVA 예제
// Base URL
String baseUrl = "https://sdk-api-v1.singular.net/api/v2/conversion_value";
// Parameters
Map < String, String > params = new HashMap < > ();
params.put("a", "sdk_key_here");
params.put("p", "iOS");
params.put("i", "com.singular.app");
params.put("v", "16.1");
params.put("idfa", "DFC5A647-9043-4699-B2A5-76F03A97064B");
params.put("idfv", "21DB6612-09B3-4ECC-84AC-B353B0AF1334");
params.put("n", "__SESSION__");
params.put("app_v", "1.2.3");
params.put("skan_current_conversion_value", "7");
params.put("p1_coarse", "0");
params.put("p2_coarse", "1");
params.put("skan_first_call_to_skadnetwork_timestamp", "1510040127");
params.put("skan_last_call_to_skadnetwork_timestamp", "1510090877");
// JSON objects
JSONObject revenueJson = new JSONObject().put("USD", 9.99);
params.put("skan_total_revenue_by_currency", revenueJson.toString());
JSONObject admonJson = new JSONObject().put("USD", 1.2);
params.put("skan_total_admon_revenue_by_currency", admonJson.toString());
// Build URL with encoded parameters
StringBuilder urlBuilder = new StringBuilder(baseUrl);
urlBuilder.append('?');
for (Map.Entry < String, String > entry: params.entrySet()) {
if (urlBuilder.length() > baseUrl.length() + 1) {
urlBuilder.append('&');
}
urlBuilder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8))
.append('=')
.append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8));
}
// Create connection
URL url = new URL(urlBuilder.toString());
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Accept", "application/json");
// Get response
int responseCode = conn.getResponseCode();
BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream())
);
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in .readLine()) != null) {
response.append(inputLine);
} in .close();
// Check application-level status
System.out.println("HTTP Status Code: " + responseCode);
System.out.println("Response: " + response.toString());
// Disconnect
conn.disconnect();
요청 응답
다음은 새로운 변환 값이 반환된 성공적인 API 응답입니다.
HTTP 응답 | |
---|---|
|
응답 본문에 오류나 이유 없이 200 - 확인이 표시되면 요청이 처리를 위해 대기열로 전송되었음을 의미합니다. 응답:
|
응답 매개변수
다음 표는 응답 매개변수를 정의합니다.
키 | 설명 | 예제 값 |
---|---|---|
|
새로운 정밀 변환 값 |
|
|
새로운 거친 변환 값 |
|
|
SKAN 포스트백 측정 기간에 해당합니다:
업데이트해야 할 거친 변환 값 키를 나타냅니다: 즉, p0_coarse, p1_coarse, p2_coarse. |
|
|
성공적으로 처리된 경우 확인 |
|
가능한 응답 오류
- 마지막 전환 업데이트 후 24시간(28032시간) 이상 경과하여 업데이트 창이 닫혔습니다:
- 다음과 같이 계산된 시간입니다:
(skan_last_call_to_skadnetwork_timestamp) - (skan_first_call_to_skadnetwork_timestamp)
- 다음과 같이 계산된 시간입니다:
- 알 수 없는 플랫폼 오류 - iOS 이외의 플랫폼
- 전환 관리: 잘못된 매개 변수 %x가 지정되었습니다.
- 전환 관리: 앱에 대한 전환 모델을 찾을 수 없습니다: %x
- 잘못된 측정 기간: %x
- 전환 관리: 소유자의 통화를 찾을 수 없습니다: %고객
선택적 파라미터
다음 표에는 SKAdNetwork 버전 4를 지원하는 데 사용되는 선택적 파라미터가 나열되어 있습니다. 나열된 모든 파라미터는 쿼리 파라미터입니다.
선택적 파라미터 | |
---|---|
전환 값 매개변수 | |
파라미터 | 설명 |
지원되는 플랫폼:
|
필수는 아닙니다. 이 키는 skan_current_conversion_value 키에서 매핑됩니다. 즉, 모델은 응답에 반환할 skan_updated_coarse_value를 평가할 때 p0_coarse를 사용하지 않고 skan_current_conversion_value를 사용합니다. 이 값은 (0-2) 사이의 정수입니다.
예제 값입니다:
|
매개변수 | 설명 |
지원되는 플랫폼:
|
p0의 이전 거친 값입니다. (0-2) 사이의 정수입니다.
예제 값입니다:
|
파라미터 | 설명 |
지원 플랫폼:
|
p1의 이전 거친 값입니다. (0-2) 사이의 정수입니다.
예제 값입니다:
|
지원되는 플랫폼:
|
p2의 이전 거친 값입니다. (0-2) 사이의 정수입니다.
예시 값:
|
구매 추적 | |
지원 플랫폼:
|
광고 구매화 구매을 제외한 p0의 IAP 구매 합계입니다.
예시 값:
|
지원되는 플랫폼:
|
광고 구매화 구매을 제외한 p1의 IAP 구매 합계.
예시 값:
|
지원 플랫폼
|
광고 구매화 구매을 제외한 p2의 IAP 구매 합계.
예시 값:
|
지원 플랫폼
|
p0에 대한 광고 구매화 구매의 총합입니다.
예시 값:
|
지원 플랫폼:
|
p1에 대한 광고 구매 창출 구매의 합계.
예시 값:
|
지원 플랫폼:
|
p2의 광고 구매 창출 구매 합계.
예시 값:
|
타임스탬프 파라미터 | |
파라미터 | 설명 |
지원되는 플랫폼:
|
p0에 대한 창 잠금이 적용된 마지막 업데이트의 유닉스 타임스탬프입니다. 참고 - Singular 변환 모델은 현재 창 잠금을 고려하지 않지만, 향후에는 고려할 수 있습니다.
예제 값입니다:
|
파라미터 | 설명 |
지원 플랫폼:
|
p1에 대한 창 잠금이 적용된 마지막 업데이트의 유닉스 타임스탬프입니다. 참고 - Singular 변환 모델은 현재 창 잠금을 고려하지 않지만, 향후에는 고려할 수 있습니다.
예제 값입니다:
|
파라미터 | 설명 |
지원 플랫폼:
|
p2에 대한 창 잠금이 적용된 마지막 업데이트의 유닉스 타임스탬프 참고 - Singular 변환 모델은 현재 창 잠금을 고려하지 않지만, 향후에는 고려할 수 있습니다.
예제 값입니다:
|