SKAdNetwork 4 구현 가이드

SKAdNetwork 4 구현 가이드

SKAdNetwork 활용 사례

SKAdNetwork(SKAN)는 사용자의 개인정보를 보호하면서 iOS 앱 인스톨 광고 캠페인을 측정할 수 있는 Apple의 개인정보 중심 어트리뷰션 프레임워크입니다. 서버 간(S2S) 구현은 서버 간에 직접 SKAdNetwork 데이터를 전송하여 캠페인 성과를 검증하고 추적하는 강력한 방법을 제공함으로써 정확한 어트리뷰션 및 전환 추적을 보장합니다.

이 프레임워크는 어트리뷰션의 모든 중요한 측면을 처리하는 동시에 Apple의 규정된 방법을 통해 사용자 개인 정보를 보호하므로, iOS 14.5 이후 환경에서 활동하는 모바일 마케터에게 필수적인 도구입니다.

주요 포인트

  • 사기 방지 기능이 내장된 모든 네트워크의 포스트백에 대한 강력한 검증 및 집계
  • 대시보드 구성을 통한 동적 전환 가치 관리
  • 강화된 마케팅 파라미터와 세분화된 데이터 인사이트를 통한 향상된 리포팅 기능
  • 디코딩된 전환 가치 및 구매 추적을 위한 안전한 파트너 포스트백 시스템
  • 강화된 이벤트 및 세션 추적을 통한 클라이언트 측 구현의 포괄적인 검증
  • 여러 포스트백 기간(0~2일, 3~7일, 8~35일)에 걸친 자동화된 타임스탬프 관리
  • 통화 사양을 통해 광고 구매화 및 정기적인 구매 추적 모두 지원

전제 조건

주요 구성 요소

Singular의 SKAdNetwork 솔루션은 다음과 같은 구성 요소로 이루어져 있습니다:

  • SKAdNetwork를 구현하기 위한 클라이언트 측 코드
    전환 값 API 엔드포인트를 사용하는 서버 측 접근 방식을 사용할 수 있습니다.
  • 모든 네트워크에서 포스트백 유효성 검사 및 집계
  • 사기 방지:
    • 서명 유효성 검사 및 트랜잭션 ID 중복 제거.
    • 서명되지 않은 파라미터(전환 값 및 지오데이터)를 검증하기 위해 네트워크를 통한 보안 설정.
  • 전환 가치 관리: Singular의 대시보드에서 동적으로 구성할 수 있는 기능을 제공하여 설치 후 활동을 SKAdNetwork 전환 가치로 인코딩할 수 있습니다.
  • 리포팅: 제한된 SKAdNetwork 캠페인 ID를 변환하고 더 많은 마케팅 파라미터와 세분화로 데이터를 보강합니다.
  • 파트너 포스트백: 최적화에 중요한 전환 가치를 이벤트 및 구매으로 디코딩하여 SKAdNetwork 포스트백을 전송합니다.

클라이언트 측 SKAdNetwork 구현은 크게 두 부분으로 구성됩니다:

  1. SKAdNetwork 클라이언트 측 구현: 이 부분은 앱을 SKAdNetwork에 등록하고 SKAdNetwork 전환 가치를 지능적으로 관리하는 데 매우 중요합니다. 즉, 이를 구현하면 SKAdNetwork 어트리뷰션 및 관련 인스톨 후 활동을 기반으로 캠페인을 최적화할 수 있습니다.
  2. 서버 측 연동 업데이트: 이 부분은 클라이언트 측 구현을 검증하고 문제를 해결하는 데 중요합니다. 세션이벤트 엔드포인트를 통해 Singular로 전송되는 이벤트와 세션을 SKAdNetwork 메타데이터로 보강함으로써, 앱 측에서 구현이 제대로 이루어졌는지 확인할 수 있습니다.

시작하기


SKAdNetwork 클라이언트 측 구현

Singular는 SKAdNetwork 등록 및 전환 값 관리를 지원하는 SKAdNetwork 인터페이스 코드 스니펫을 제공합니다. 이 코드 샘플은 다음과 같은 부분을 담당합니다:

  1. SKAdNetwork 지원 및 등록
  2. 전환 가치 관리:
    • 이 코드는 Singular의 엔드포인트와 동기식으로 통신하여 설정된 전환 모델에 따라 다음 전환 값을 수신합니다. 이벤트/세션/구매을 보고하고, 이에 대한 응답으로 다음 전환 값을 가져오는데, 이는 Singular의 대시보드에서 측정하도록 구성된 설치 후 활동을 나타내는 인코딩된 숫자입니다.
    • 이 코드는 또한 측정 기간별로 SKAdnetwork 메타데이터를 수집합니다. 이 메타데이터는 유효성 검사와 다음 전환 값 계산에 모두 사용됩니다:
      • 기본 SKAdNetwork 프레임워크에 대한 첫 번째 호출 타임스탬프
      • 기본 SKAdNetwork 프레임워크에 대한 마지막 호출 타임스탬프
      • 마지막으로 업데이트된 포스트백 값(거칠게 및 세밀하게 모두)
      • 디바이스에서 생성된 총 구매 및 총 애드몬 구매

연동 흐름

Screen_Shot_2020-09-16_at_18.59.13.png

위의 다이어그램은 S2S 고객에 대한 SKAdNetwork의 흐름을 보여줍니다:

  1. 첫째, 앱의 코드는 앱에서 발생하는 이벤트/세션/구매 이벤트에 따라 Singular 서버(SKAdNetwork 전용 엔드포인트를 통해)와 통신하여 최신 전환 값을 동기적으로 가져오고, 이 값으로 SKAdNetwork 프레임워크를 업데이트합니다.
  2. 둘째, 앱은 기존 이벤트와 세션을 SKAdNetwork 데이터로 보강하여 나중에 유효성 검사에 사용합니다.
  3. 앱이 새로운 전환 값으로 SKAdNetwork 프레임워크 업데이트를 완료하고 SKAdNetwork 타이머가 만료되면, SKAdNetwork 포스트백이 네트워크에 전송됩니다.
  4. 네트워크는 보안 설정 또는 일반 설정을 통해 이를 Singular로 전달합니다.
  5. Singular는 포스트백을 처리합니다:
    • 서명 검증
    • 구성된 전환 모델을 기반으로 전환 값을 디코딩합니다.
    • 네트워크 정보로 포스트백을 보강합니다. 데이터는 파트너와의 연동을 통해 SKAdNetwork 및 네트워크 캠페인 ID에 가입하여 수집합니다.
    • 디코딩된 포스트백을 BI 및 파트너에게 전송합니다.

중요한 점은 인스톨 및 디코딩된 이벤트를 포함한 SKAdNetwork 정보는 기존 데이터 세트와 혼용되지 않도록 다른 보고서/API/ETL 테이블 및 포스트백을 통해 액세스할 수 있다는 것입니다. 이는 특히 다음 몇 주 동안 기존 캠페인 활동과 SKAdNetwork를 나란히 측정하고 테스트할 수 있도록 하는 데 중요합니다.

SKAdNetwork 인터페이스

이 헤더 파일은 iOS 앱에서 어트리뷰션 추적, 전환 가치 업데이트, 구매 관리를 위한 방법을 제공하는 SKAdNetwork(SKAN) 연동을 위한 퍼블릭 인터페이스를 정의합니다.

SKANSnippet.h 코드 샘플

어트리뷰션 등록

앱이 처음 실행될 때 SKAN 어트리뷰션 추적을 초기화하여 초기 전환 값을 0으로 설정하고 기준 타임스탬프를 설정합니다. 기본 Apple API 메서드는 디바이스에 해당 앱에 대한 어트리뷰션 데이터가 있는 경우 알림을 생성합니다.

Objective-C
+ (void)registerAppForAdNetworkAttribution;

전환 가치 관리

  • 전환 값은 아래 메소드를 통해 캡처된 디바이스의 설치 후 활동과 사용자가 동적으로 구성할 수 있는 선택한 전환 모델을 기반으로 계산됩니다.
  • 이 섹션의 메서드는 선택한 전환 모델과 보고된 설치 후 활동에 따라 Singular의 엔드포인트에서 다음 전환 값을 가져옵니다(위 문서 참조).
  • 아래 방법은 다음과 같은 인스톨 후 활동을 기반으로 전환 값을 업데이트합니다:
    • 세션: SKAdNetwork를 사용한 사용자의 리텐션 측정에 중요합니다.
    • 전환 이벤트: SKAdNetwork를 통한 인스톨 후 전환 이벤트 측정에 중요합니다.
    • 구매 이벤트: SKAdNetwork를 통한 구매 측정에 필수적입니다.

세션 추적

리텐션 및 코호트 분석을 위한 세션 기반 추적과 업데이트 후 액션을 위한 완료 핸들러(선택 사항)를 관리합니다.

Objective-C
+ (void)updateConversionValuesAsync:(void(^)(NSNumber *, NSNumber *, BOOL, NSError *))handler;

이벤트 트래킹

Singular로 데이터를 전송하기 전에 전환 이벤트 추적을 처리하여 이벤트 컨텍스트에 따라 전환 값을 업데이트합니다.

Objective-C
+ (void)updateConversionValuesAsync:(NSString *)eventName 
                withCompletionHandler:(void(^)(NSNumber *, NSNumber *, BOOL, NSError *))handler;

구매 관리

구매 이벤트를 추적하여 광고 구매화와 일반 구매에 대한 별도의 합계를 유지하며, 전환 값 업데이트 전에 호출이 필요합니다.

Objective-C
+ (void)updateRevenue:(double)amount 
          andCurrency:(NSString *)currency 
     isAdMonetization:(BOOL)admon;

데이터 검색

다음을 포함한 포괄적인 SKAN 데이터를 반환합니다:

  • 현재 및 이전의 세분화된 전환 값
  • 다양한 포스트백 윈도우에 걸친 대략적인 값
  • 윈도우 잠금 타임스탬프
  • 통화별 구매 추적
  • 광고 구매화 및 일반 구매에 대한 별도 추적
Objective-C
+ (NSDictionary *)getSkanDetails;

구현 참고 사항

  • 메서드는 메인 스레드 차단을 방지하기 위해 비동기 패턴을 사용합니다.
  • 구매 추적은 전환 가치 업데이트보다 선행되어야 합니다.
  • 세분화된(0-63) 전환 값과 거친(낮음/중간/높음) 전환 값 모두 지원
  • 다양한 포스트백 기간에 대해 별도의 추적 유지
  • 완료 핸들러를 통해 포괄적인 오류 처리 구현

SKANSnippet.h 인터페이스 코드

Objective-C
//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) 인터페이스를 구현합니다.

SKANSnippet.m 코드 샘플

상수 및 구성

이 구현은 사용자 활동과 전환을 추적하기 위한 세 가지 포스트백 윈도우를 정의합니다.

Objective-C
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 요청에 쿼리 파라미터로 추가합니다.

Objective-C
NSDictionary *values = [SKANSnippet getSkanDetails];

SKANSnippet.m 구현 코드

Objective-C
//  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의 엔드포인트로 전송합니다. 이는 어트리뷰션 추적을 설정하기 위해 앱을 처음 실행할 때만 실행됩니다.

이 코드는 어트리뷰션 추적을 설정하기 위해 앱을 처음 실행할 때만 실행됩니다.

Objective-C
[SKANSnippet registerAppForAdNetworkAttribution];
NSDictionary *skanValues = [SKANSnippet getSkanDetails];
[self sendSessionToServer:skanValues] //to Singular launch EP 

세션 관리

이 섹션은 각 세션 후 전환 값을 업데이트하고 업데이트된 SKAN 세부 정보를 전송하여 사용자 참여를 추적합니다.

Objective-C
[SKANSnippet updateConversionValuesAsync:handler];
NSDictionary *skanValues = [SKANSnippet getSkanDetails];
[self sendSessionToServer:skanValues]

이벤트 추적

이 코드는 전환 값을 업데이트하고 이벤트 데이터를 Singular의 이벤트 엔드포인트로 전송하여 비구매 이벤트를 처리합니다.

Objective-C
[SKANSnippet updateConversionValuesAsync:@"event_name" withCompletionHandler:handler];
NSDictionary *skanValues = [SKANSnippet getSkanDetails];
[self sendEventToServer:skanValues eventName:@"event_name"]

구매 추적

이 섹션에서는 통화로 구매 금액과 관련 전환 값을 모두 업데이트하여 구매 이벤트를 관리합니다. 그런 다음 데이터는 구매 관련 활동을 추적하기 위해 Singular의 이벤트 엔드포인트로 전송됩니다.

Objective-C
[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 전환 가치는 두 가지 방법을 통해 보고할 수 있습니다:

  1. 클라이언트 측에서 직접 SKAdNetwork 인터페이스 구현 - 위 참조
  2. 전환 가치 API 엔드포인트를 사용한 서버 측 연동

두 가지 방법 모두 동일한 데이터 흐름과 보고 무결성을 유지하므로, 기술 아키텍처에 가장 적합한 구현 방식을 선택할 수 있습니다. 전환 가치 API 엔드포인트는 클라이언트 측 인터페이스와 동일한 파라미터를 허용하므로 일관된 어트리뷰션 추적을 보장합니다.

전환 가치 API 참조

내용


전환 값 API 엔드포인트

HTTP 메서드 및 변환 값 엔드포인트

GET https://sdk-api-v1.singular.net/api/v2/conversion_value

필수 파라미터

다음 표에는 서버에서 전환 API를 지원하기 위한 필수 및 선택 매개변수가 나열되어 있습니다. 나열된 모든 매개변수는 쿼리 매개변수입니다.

필수 매개변수
API 키
파라미터 설명
a
string

a 매개변수는 Singular SDK 키를 지정합니다.

메인 메뉴의 개발자 도구 아래 Singular UI에서 SDK 키를 검색합니다.

참고: 보고 API 키는 데이터가 거부될 수 있으므로 사용하지 마세요.

 

예시 값:
sdkKey_afdadsf7asf56
디바이스 식별자 매개변수
파라미터 설명
idfa
string

광고주가 사용자 행동(예: 광고 클릭, 앱 설치)을 추적하고 특정 캠페인에 어트리뷰션하여 정확한 광고 타겟팅 및 캠페인 최적화를 가능하게 하는 IDFA(광고주 식별자) 를 지정하는 매개변수입니다.

iOS 14.5부터는 앱이 IDFA에 액세스하기 전에 사용자가 ATT(앱 추적 투명성) 프레임워크를 통해 옵트인해야 합니다. 사용자가 IDFA에 옵트인하지 않으면 IDFA를 사용할 수 없게 되어 추적 기능이 제한됩니다.

  • IDFA를 사용할 수 없는 경우 요청에서 매개 변수를 생략하세요.
  • 요청에 NULL 또는 빈 문자열을 전달하지 마세요.
  • IDFA 식별자를 검색하는 방법

 

예제 값:
DFC5A647-9043-4699-B2A5-76F03A97064B
파라미터 설명
idfv
string

idfv 매개변수는 특정 공급업체 또는 개발자에게만 해당하는 Apple이 장치에 할당하는 고유 식별자인 IDFV(공급업체 식별자)를 지정합니다. 특정 기기에서 동일한 공급업체의 모든 앱에서 일관성을 유지하므로 공급업체는 사용자를 개인적으로 식별하지 않고도 앱 생태계 전반에서 사용자 행동과 상호 작용을 추적할 수 있습니다.

 

예제 값:
21DB6612-09B3-4ECC-84AC-B353B0AF1334
장치 매개변수
파라미터 설명
p
string

p 매개변수는 앱의 플랫폼을 지정합니다. 이 API는 iOS에서만 사용되므로 이 값은 iOS여야 합니다.

 

값 예시:
iOS
매개변수 설명
v
string

v 매개변수는 세션 시점의 디바이스 OS 버전을 지정합니다.

 

예제 값입니다:
16.1
애플리케이션 매개변수
매개변수 설명
i
string

i 매개변수는 앱 식별자를 지정합니다.

이는 iOS 애플리케이션의 번들 ID입니다. (대소문자 구분)

예제 값입니다:
com.singular.app
파라미터 설명
app_v
string

app_v 매개변수는 애플리케이션 버전을 지정합니다.

 

예시:
1.2.3
이벤트 매개변수
파라미터 설명
n
string

n 매개변수는 추적 중인 이벤트의 이름을 지정합니다.

  • 제한: 최대 32개의 ASCII 문자
  • 세션의 경우 이벤트 이름을 사용합니다:
    __SESSION__
  • 세션이 아닌 이벤트의 경우, 이벤트 API 엔드포인트를 통해 Singular로 전송된 동일한 이벤트 이름과 대소문자를 사용합니다.

 

예시 값:
sng_add_to_cart
전환 값 매개변수
파라미터 설명
skan_current_conversion_value

지원되는 플랫폼:

  • iOS 15.4+
int

이전 세션/이벤트 알림 시점의 최신 SKAdNetwork 전환 값입니다. (0-63) 사이의 정수입니다.

 

예시 값입니다:

7
파라미터 설명
p1_coarse

지원 플랫폼:

  • iOS 16.1+
int

이전 세션/이벤트 알림 시점의 postback_sequence 1에 대한 최신 SKAdNetwork 거친 변환 값입니다. 이 값은 (0-2) 사이의 정수입니다.

 

예시 값입니다:

0
파라미터 설명
p2_coarse

지원 플랫폼:

  • iOS 16.1+
int

이전 세션/이벤트 알림 시점의 postback_sequence 2에 대한 최신 SKAdNetwork 거친 변환 값입니다. 이 값은 (0-2) 사이의 정수입니다.

 

예시 값입니다:

1
구매 추적 파라미터
파라미터 설명
skan_total_revenue_by_currency

지원 플랫폼:

  • iOS 15.4+
JSON URL-encoded string

IAP 또는 모든 구매 모델을 사용하는 경우 필수입니다. 광고 구매화 구매을 제외한 기기에서 발생한 IAP 구매의 현재 집계된 총합계입니다.

{
   "USD":9.99
}

 

예시 값입니다:

%7B%22USD%22%3A9.99%7D
파라미터 설명
skan_total_admon_revenue_by_currency

지원 플랫폼:

  • iOS 15.4+
JSON URL-encoded string

애드몬 또는 모든 구매 전환 모델을 사용하는 경우 필수입니다. 기기에서 발생한 광고 구매 창출 구매의 현재 집계된 총합입니다.

{
   "USD":1.2
}

 

예시 값입니다:

%7B%22USD%22%3A5%7D
타임스탬프 파라미터
파라미터 설명
skan_first_call_to_skadnetwork_timestamp

지원되는 플랫폼:

  • iOS 15.4+
int

기본 SKAdNetwork API에 대한 첫 번째 호출의 유닉스 타임스탬프입니다.

 

예시 값입니다:

1483228800
파라미터 설명
skan_last_call_to_skadnetwork_timestamp

지원 플랫폼:

  • iOS 15.4+
int

이 세션 알림이 발생한 시점의 기본 SKAdNetwork API에 대한 최신 호출의 유닉스 타임스탬프입니다.

 

예시 값입니다:

1483228800

요청 본문

이 메서드를 호출할 때 요청 본문은 제공하지 마세요. 요청은 쿼리 파라미터와 함께 GET 메서드를 사용하여 전송해야 합니다.

 

요청 예제

다음 코드 샘플은 지원되는 모든 매개변수를 나타내지 않을 수 있습니다. 요청을 구현할 때는 위에 나열된 모든 필수 매개변수를 포함하고 프로덕션 인스턴스에서 데이터를 전송하기 전에 올바른 값이 전달되고 있는지 확인하세요. 개발 및 테스트를 위해 고유한 `i` 매개변수(애플리케이션 식별자)를 사용하는 것이 좋습니다.

 

PYTHON CURL HTTP JAVA

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())

 

요청 응답

다음은 새로운 변환 값이 반환된 성공적인 API 응답입니다.

HTTP 응답
200 - ok

응답 본문에 오류나 이유 없이 200 - 확인이 표시되면 요청이 처리를 위해 대기열로 전송되었음을 의미합니다.

 

응답:
{
   "conversion_value":1,
   "skan_updated_coarse_value":0,
   "postback_sequence_index":0,
   "status":"ok"
}

응답 매개변수

다음 표는 응답 매개변수를 정의합니다.

설명 예제 값
conversion_value

새로운 정밀 변환 값

0-63
skan_updated_coarse_value

새로운 거친 변환 값

0-2
postback_sequence_index

SKAN 포스트백 측정 기간에 해당합니다:

  • 0 = 포스트백 1
  • 1 = 포스트백 2
  • 2 = 포스트백 3

업데이트해야 할 거친 변환 값 키를 나타냅니다: 즉, p0_coarse, p1_coarse, p2_coarse.

0-2
status

성공적으로 처리된 경우 확인

ok

가능한 응답 오류

  • 마지막 전환 업데이트 후 24시간(28032시간) 이상 경과하여 업데이트 창이 닫혔습니다:
    • 다음과 같이 계산된 시간입니다:
      (skan_last_call_to_skadnetwork_timestamp) - (skan_first_call_to_skadnetwork_timestamp)
  • 알 수 없는 플랫폼 오류 - iOS 이외의 플랫폼
  • 전환 관리: 잘못된 매개 변수 %x가 지정되었습니다.
  • 전환 관리: 앱에 대한 전환 모델을 찾을 수 없습니다: %x
  • 잘못된 측정 기간: %x
  • 전환 관리: 소유자의 통화를 찾을 수 없습니다: %고객

선택적 파라미터

다음 표에는 SKAdNetwork 버전 4를 지원하는 데 사용되는 선택적 파라미터가 나열되어 있습니다. 나열된 모든 파라미터는 쿼리 파라미터입니다.

선택적 파라미터
전환 값 매개변수
파라미터 설명
p0_coarse

지원되는 플랫폼:

  • iOS 16.1+
int

필수는 아닙니다. 이 키는 skan_current_conversion_value 키에서 매핑됩니다. 즉, 모델은 응답에 반환할 skan_updated_coarse_value를 평가할 때 p0_coarse를 사용하지 않고 skan_current_conversion_value를 사용합니다. 이 값은 (0-2) 사이의 정수입니다.

 

예제 값입니다:

0
매개변수 설명
p0_prev_coarse_value

지원되는 플랫폼:

  • iOS 16.1+
int

p0의 이전 거친 값입니다. (0-2) 사이의 정수입니다.

 

예제 값입니다:

0
파라미터 설명
p1_prev_coarse_value

지원 플랫폼:

  • iOS 16.1+
int

p1의 이전 거친 값입니다. (0-2) 사이의 정수입니다.

 

예제 값입니다:

0
p2_prev_coarse_value

지원되는 플랫폼:

  • iOS 16.1+
int

p2의 이전 거친 값입니다. (0-2) 사이의 정수입니다.

 

예시 값:

0
구매 추적
p0_total_iap_revenue

지원 플랫폼:

  • iOS 16.1+
JSON URL-encoded string

광고 구매화 구매을 제외한 p0의 IAP 구매 합계입니다.

{
   "USD":9.99
}

 

예시 값:

%7B%22USD%22%3A9.99%7D
p1_total_iap_revenue

지원되는 플랫폼:

  • iOS 16.1+
JSON URL-encoded string

광고 구매화 구매을 제외한 p1의 IAP 구매 합계.

{
   "USD":9.99
}

 

예시 값:

%7B%22USD%22%3A9.99%7D
p2_total_iap_revenue

지원 플랫폼

  • iOS 16.1+
JSON URL-encoded string

광고 구매화 구매을 제외한 p2의 IAP 구매 합계.

{
   "USD":9.99
}

 

예시 값:

%7B%22USD%22%3A9.99%7D
p0_total_admon_revenue

지원 플랫폼

  • iOS 16.1+
JSON URL-encoded string

p0에 대한 광고 구매화 구매의 총합입니다.

{
   "USD":1.2
}

 

예시 값:

%7B%22USD%22%3A1.2%7D
p1_total_admon_revenue

지원 플랫폼:

  • iOS 16.1+
JSON URL-encoded string

p1에 대한 광고 구매 창출 구매의 합계.

{
   "USD":1.2
}

 

예시 값:

%7B%22USD%22%3A1.2%7D
p2_total_admon_revenue

지원 플랫폼:

  • iOS 16.1+
JSON URL-encoded string

p2의 광고 구매 창출 구매 합계.

{
   "USD":1.2
}

 

예시 값:

%7B%22USD%22%3A1.2%7D
타임스탬프 파라미터
파라미터 설명
p0_window_lock

지원되는 플랫폼:

  • iOS 16.1+
int

p0에 대한 창 잠금이 적용된 마지막 업데이트의 유닉스 타임스탬프입니다.

참고 - Singular 변환 모델은 현재 창 잠금을 고려하지 않지만, 향후에는 고려할 수 있습니다.

 

예제 값입니다:

1483228850
파라미터 설명
p1_window_lock

지원 플랫폼:

  • iOS 16.1+
int

p1에 대한 창 잠금이 적용된 마지막 업데이트의 유닉스 타임스탬프입니다.

참고 - Singular 변환 모델은 현재 창 잠금을 고려하지 않지만, 향후에는 고려할 수 있습니다.

 

예제 값입니다:

1483228850
파라미터 설명
p2_window_lock

지원 플랫폼:

  • iOS 16.1+
int

p2에 대한 창 잠금이 적용된 마지막 업데이트의 유닉스 타임스탬프

참고 - Singular 변환 모델은 현재 창 잠금을 고려하지 않지만, 향후에는 고려할 수 있습니다.

 

예제 값입니다:

1483228850