iOS ATT와 SKAN은 무엇부터 준비해야 하나요?
ATT 동의율을 올리는 프롬프트 설계와, KPI에 맞춘 SKAN 컨버전 값 스키마입니다.
iOS 프라이버시 대응 (ATT & SKAN)
ATT 프롬프트 허용률을 끌어올리고, SKAdNetwork 4.0 Conversion Value 스키마를 비즈니스 KPI에 맞춰 설계한다. ATT 허용률 0.1pp 차이가 iOS CPI 5~10% 영향으로 직결된다.
ATT(App Tracking Transparency)는 IDFA 사용 동의를 묻는 시스템 프롬프트이며, 미허용 시 어트리뷰션이 SKAdNetwork(SKAN) 단일 채널로 강제 다운그레이드된다. SKAN 4.0은 3개 시점(0~2일/3~7일/8~35일)에 Coarse(low/medium/high) + Fine(0~63) 값을 전송한다. 본 가이드는 (1) ATT 프롬프트 최적화, (2) SKAN CV 스키마 설계, (3) Adjust 매핑 셋업을 다룬다.
§1ATT 프롬프트 설계
업계 평균 ATT 허용률: 게임 ~28%, 커머스 ~35%, 핀테크 ~42% (2025 기준). 허용률 끌어올리는 3가지 레버:
- Pre-prompt(설명 다이얼로그) 삽입 — 시스템 프롬프트 호출 직전 자체 디자인 화면에서 "왜 동의가 필요한지"를 카피로 설명. 거부 의도가 있는 사용자는 Pre-prompt에서 걸러 시스템 프롬프트 거부율을 낮춤.
- 호출 시점 지연 — 첫 실행 즉시가 아니라 가치 인지 후(온보딩 완료 직후, 첫 구매 전, 푸시 동의 직후 등) 호출. 30~60초 지연 권장.
- NSUserTrackingUsageDescription 카피 — Info.plist의 ATT 설명 문구. "개인 맞춤 광고" 대신 "더 적합한 콘텐츠 추천" 등 사용자 효익 중심 카피.
import AppTrackingTransparency
import AdjustSdk
func showATTFlow(from vc: UIViewController) {
// 1. 가치 인지 시점 체크 (예: 온보딩 완료)
guard OnboardingState.shared.isCompleted else { return }
// 2. Pre-prompt 표시
let pre = PrePromptViewController(
title: "개인화된 콘텐츠 추천을 위한 동의",
body: "다음 화면에서 '허용'을 선택하시면 더 관심에 맞는 콘텐츠를 추천드릴 수 있습니다.",
primaryAction: "계속",
secondaryAction: "지금은 건너뛰기"
)
pre.onPrimary = { requestSystemATT() }
pre.onSecondary = { /* 7일 후 재시도 큐잉 */ }
vc.present(pre, animated: true)
}
func requestSystemATT() {
ATTrackingManager.requestTrackingAuthorization { status in
// Adjust SDK는 결과를 자동 수신
Analytics.track("att_response", [
"status": status.rawValue,
"is_authorized": status == .authorized
])
}
}
{
"NSUserTrackingUsageDescription":
"더 관심에 맞는 콘텐츠와 혜택을 추천드리기 위해 활동 데이터를 사용합니다."
}
§2SKAdNetwork 4.0 스키마
SKAN 4.0의 핵심 변경점:
- 3-postback 윈도우: 설치 후 0~2일 / 3~7일 / 8~35일에 각각 1개씩 postback 발화
- Coarse Conversion Value:
low/medium/high3단계 — 트래픽이 적은 캠페인도 신호 수집 가능 - Fine Conversion Value: 0~63 (6bit) — 충분한 트래픽이 있는 캠페인만 허용
- Hierarchical Source ID: 캠페인/광고그룹/소재 단위 시그널 가능 (이전 2-digit → 4-digit)
§3SKAN은 어느 광고에 설치를 주는가
SKAN 숫자를 두고 벌어지는 논쟁은 사실 대부분 이 단계에 관한 것이다. SKAdNetwork는 기여를 나눠 갖지 않는다 — 애플이 설치 하나당 광고 하나만 고르고 나머지는 0을 받는다. 그 승자가 어떻게 정해지는지를 알아야 나중에 마주칠 숫자 차이를 설명할 수 있다.
- 광고 네트워크가 노출·클릭을 자기 SKAdNetwork 키로 서명해서 내보낸다.
- 설치가 일어나면 애플이 어느 서명 광고에 귀속할지 정한다. 네트워크도 MMP도 아니다.
- 클릭이 뷰스루를 이긴다. 같은 종류끼리는 더 최근 것이 이긴다.
- postback은 이긴 네트워크의 등록 엔드포인트로 간다. 앱이 개발자용 postback URL을 등록해야 사본을 받고, 그 사본은 네트워크가 받는 것보다 정보가 적다.
postback이 늦게 오는 건 설계다
postback은 윈도우가 닫히는 순간 발송되지 않는다. 애플이 무작위 지연을 얹는다 — 도착 시각으로 사용자를 특정하지 못하게 하려는 것이다. 그래서 설치와 그 postback은 서로 다른 주에 잡히고, 월요일에 끈 캠페인이 며칠간 계속 postback을 만든다. 윈도우와 지연의 정확한 값은 버전마다 바뀌었으므로, 블로그에 적힌 숫자(이 문서 포함)를 믿지 말고 애플 SKAdNetwork 문서에서 현재 값을 확인한다.
AdAttributionKit이 SKAdNetwork와 나란히 놓였고 네트워크별 지원이 다르다. 스키마를 다시 짜기 전에 각 네트워크가 실제로 어느 쪽을 읽는지 확인한다.
MMP 대시보드와 네트워크 대시보드가, 때로는 크게 어긋나는 이유도 이것이다. 어느 쪽이 거짓말을 하는 게 아니라 서로 다른 것을 서로 다른 시계로 세고 있다. 버그로 올리기 전에 SKAN 어트리뷰션과 MMP 숫자가 다른 이유에서 각 숫자의 출처를 먼저 확인한다.
§4Crowd anonymity (프라이버시 임계)
Crowd anonymity는 재식별을 막는 애플의 장치다. postback 뒤에 깔린 설치 수가 너무 적으면 애플이 정보를 덜 준다. 스키마를 제대로 짜고도 값이 비어서 오는 가장 흔한 원인이다.
- 낮은 tier → fine Conversion Value가 아예 오지 않고 Source ID도 가장 거친 형태로 잘린다.
- 높은 tier → fine 값과 Source ID 자릿수가 열린다.
- tier는 postback 맥락마다 다시 평가된다. 캠페인 구조와 일별 볼륨에 따라 움직이는 값이지 한 번 정해두는 설정이 아니다.
구조를 바꾼 뒤 fine 값이 끊겼다면 SDK보다 tier를 먼저 의심한다. 캠페인을 합쳐 볼륨을 회복시키고 다시 확인한 다음에 코드를 건드린다.
애플은 tier 임계값을 공개하지 않고 규칙도 버전마다 바뀐다. 구조를 확정하기 전에 애플 문서에서 확인한다.
§5Conversion Value 매핑 (커머스 앱 예시)
매출 누적액 + 핵심 이벤트 발생 여부의 조합으로 64개 슬롯에 매핑한다. 0~2일차에 Fine 64단계, 3~7일차와 8~35일차에 Coarse 3단계만 활용 가능.
| Fine CV | 조건 | Coarse | 비즈니스 의미 |
|---|---|---|---|
| 0 | install only | low | 설치만 |
| 1 | session_start ≥ 2 | low | 재방문 |
| 5 | add_to_cart 1회 | low | 관여 시작 |
| 10 | add_to_cart ≥ 2 OR begin_checkout | medium | 고관여 |
| 20 | purchase 1회 · revenue < $5 | medium | 저액 구매 |
| 30 | purchase 1회 · revenue $5~$20 | medium | 중간 구매 |
| 45 | purchase 1회 · revenue $20~$50 | high | 고액 구매 |
| 55 | purchase ≥ 2 OR revenue ≥ $50 | high | 충성 구매 |
| 63 | subscribe (월 구독) | high | 구독 전환 |
§6집계되고 늦게 오는 데이터로 운영하기
SKAN은 데이터를 줄이기만 하는 게 아니라 판단의 단위를 바꾼다. 안드로이드를 최적화하던 방식 그대로 iOS 캠페인을 만지면 확신에 찬 오판이 나온다.
지출일이 아니라 설치 코호트로 읽는다
지출은 결제한 날에 잡히고, 짝이 되는 postback은 윈도우가 닫히고 무작위 지연이 지난 뒤에야 온다. 오늘 지출을 오늘 postback으로 나누면 서로 다른 모집단의 CPI가 나온다. 모든 비율을 설치 코호트에 붙여 계산하고, 윈도우+지연이 다 지나기 전의 최근 며칠은 미완성으로 둔다.
쪼개기 전에 합친다
- 캠페인 하나를 여럿으로 쪼개는 것이 멀쩡한 스키마에서 null이 오기 시작하는 경로다. 조각마다 따로 crowd anonymity tier 아래로 떨어진다.
- 받을 수 없는 세분화는 세분화가 아니다. fine 값이 돌아올 때까지 합치고, 볼륨이 허락하는 만큼만 다시 쪼갠다.
- 구조를 바꿀 때마다 다시 확인한다. tier는 셋업 때 한 번이 아니라 볼륨을 따라 재평가된다.
실제로 받는 신호에 맞춰 최적화한다
postback 대부분이 coarse 값으로 온다면, fine 값 기반 매출 구간으로 짠 입찰 전략은 대체로 존재하지 않는 데이터를 향해 최적화하고 있는 것이다. 분포부터 확인한다. Conversion Value가 압도적으로 0이면 보통은 유저가 무가치한 게 아니라 첫 윈도우가 매핑한 이벤트에 도달하기 전에 닫히는 것이다.
집계되고 지연된 라스트터치 postback은 무엇이 귀속됐는지를 말할 뿐 무엇이 증분이었는지는 말하지 않는다. 지출이 수요를 만들었는지를 물어야 한다면 홀드아웃이나 지역 실험이 필요하다 — 증분 측정 글과 증분 분석 도구를 참고한다.
실무 결론 둘. 변경은 윈도우+지연이 온전히 지난 뒤에 판단한다. 그리고 네트워크 비교는 실제로 받은 postback으로만 하고, 네트워크가 자체 모델링한 설치를 섞어서 비교하지 않는다.
§7Adjust SKAN 셋업
- Adjust 대시보드 → App Setup → SKAdNetwork →
Enable SKAN 4.0 - Conversion Value Schema: Revenue + Events 조합 모드 선택
- 이벤트별 Coarse Tier 매핑 (위 표 §3 기준)
- 매체별 SKAN 채널 활성화: Meta/Google/TikTok/AppLovin 별도 토글
- Apple Console에서 Test Mode로 postback 발화 검증 (xcrun simctl)
class SKANDelegateHandler: NSObject, AdjustDelegate {
static let shared = SKANDelegateHandler()
func adjustConversionValueUpdated(_ conversionValue: NSNumber?) {
// Adjust가 자동으로 conversion value 계산 후 호출
guard let cv = conversionValue?.intValue else { return }
Analytics.track("skan_cv_updated", [
"fine_value": cv,
"coarse_tier": cv < 10 ? "low" : cv < 45 ? "medium" : "high"
])
}
}
§8트러블슈팅
1) NSUserTrackingUsageDescription Info.plist 누락. 2) iOS 14.5 미만 디바이스. 3) 이미 응답한 사용자(설정 → 개인정보 보호 → 추적에서 초기화 후 재테스트). 4) Adjust SDK 초기화가 ATT 호출 이전에 완료되어야 함.
1) Apple Console의 SKAdNetwork Identifier(.skadnetworkidentifier)가 Info.plist에 등록되었는지 확인 — 매체별 식별자가 모두 들어가야 함 (보통 30~50개). 2) Adjust는 자동으로 매체사 redirect를 처리하지만, 매체사가 SKAN 채널을 별도 활성화해야 함. 3) postback 지연 정상 범위: 0~24시간.
CV 매핑이 너무 빡빡하게 설정된 경우. 매출 임계값을 낮추거나, 이벤트만으로도 CV가 올라가도록 mid-tier 슬롯을 확장. Apple Privacy Threshold(25 install/일) 미달 캠페인은 CV가 NULL로 마스킹되므로 작은 캠페인은 합쳐서 운영.
관련 자료