개발자 협업 가이드 및 테크니컬 PRD
MMP(Adjust) SDK 초기화 스펙, 딥링크 라우팅 로직, 그리고 릴리즈 전 QA 시나리오를 단일 문서로 통합한 개발 협업 표준. 본 문서가 그대로 PRD가 되도록 작성됨.
본 문서는 신규 앱 또는 리뉴얼 앱의 어트리뷰션 기반 마련을 위해 개발팀이 구현해야 할 최소 필수 스펙을 정의한다. Adjust SDK v5 초기화 → 디퍼드 딥링크 라우팅 → 릴리즈 QA 체크리스트의 3단 게이트를 통과한 빌드만 LIVE 캠페인에 진입한다. 잘못 셋업된 SDK 한 줄이 전 캠페인 데이터를 무효화시키므로, 모든 항목은 코드 리뷰 + QA Lead 더블 사인오프를 강제한다.
§1사전 요구사항
- Adjust 대시보드 권한: App Token (iOS/Android 분리), Environment(sandbox/production), Default Tracker 발급 완료
- Bundle ID / Package Name 사전 등록. Android는
applicationId기준, iOS는 App Store Connect의Bundle Identifier기준 - StoreKit 2 + ATTrackingManager 의존성 및 iOS 14.5+ 지원
- App Links / Universal Links 도메인 검증 파일 호스팅 (
.well-known/apple-app-site-association,.well-known/assetlinks.json)
§2구현 단계
2.1 iOS · Adjust SDK 초기화 (Swift, AppDelegate)
초기화는 반드시 ATT 프롬프트 응답 후 진행한다. 프롬프트 이전 초기화 시 IDFA가 영구적으로 0으로 고정되어 attribution이 SKAdNetwork 단일 채널로 강제 다운그레이드된다.
import Adjust
import AdjustSdk
import AppTrackingTransparency
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
let appToken = "abc1234xyz9" class="c">// Production token
let environment = ADJEnvironmentProduction
let config = ADJConfig(appToken: appToken,
environment: environment,
allowSuppressLogLevel: false)
config?.logLevel = ADJLogLevelInfo
config?.needsCost = true class="c">// ROAS 계산용 비용 데이터
config?.linkMeMetadata = true class="c">// LinkMe 클립보드 매칭 활성화
class="c">// attConsentWaitingInterval: Adjust가 ATT 응답을 기다리는 최대 시간(초).
class="c">// 응답이 늦어도 첫 install/session 이벤트를 발화시키는 fallback 타이머이며,
class="c">// ATT 프롬프트 자체의 표시 시간과는 무관 (App Store 가이드라인과 별개 영역).
class="c">// 120초 = ATT 응답 확보율 최대화 우선 운영.
config?.attConsentWaitingInterval = 120
class="c">// SKAN 4.0 ConversionValue 업데이트 위임
config?.delegate = SKANDelegateHandler.shared
Adjust.appDidLaunch(config)
class="c">// ATT 프롬프트는 사용자가 앱의 가치를 인지한 시점에 호출 (온보딩 완료 후 등).
class="c">// 첫 실행 즉시 호출하면 거부율↑, 또한 너무 빠른 호출은 사용자 컨텍스트 부재로 reject 사유.
class="c">// 1-4 가이드의 ATT 디자인 패턴 참고.
OnboardingState.shared.onCompleted = {
ATTrackingManager.requestTrackingAuthorization { _ in
Adjust.requestTrackingAuthorization { status in
class="c">// status: 0 Not Determined / 3 Authorized
print("[ATT] status=\(status)")
}
}
}
return true
}
2.2 Android · Adjust SDK 초기화 (Kotlin, Application 클래스)
Application 서브클래스에서 최상위 onCreate에 위치시켜야 한다. 외부 라이브러리(Firebase 등)보다 늦게 초기화될 경우, 첫 세션의 install referrer가 누락된다.
import android.app.Application
import com.adjust.sdk.Adjust
import com.adjust.sdk.AdjustConfig
import com.adjust.sdk.LogLevel
class App : Application() {
override fun onCreate() {
super.onCreate()
val appToken = "abc1234xyz9"
val environment = AdjustConfig.ENVIRONMENT_PRODUCTION
val config = AdjustConfig(this, appToken, environment).apply {
setLogLevel(LogLevel.INFO)
setNeedsCost(true)
setPreinstallTrackingEnabled(true) class="c">// 사전탑재 트래킹
setSendInBackground(true) class="c">// 백그라운드 전송
setEventBufferingEnabled(false) class="c">// 실시간 캠페인 운영 시 false
setOnAttributionChangedListener { attr ->
class="c">// network_name / campaign_name / adgroup_name / creative_name
AppAnalytics.bindAttribution(attr)
}
setOnDeeplinkResponseListener { uri ->
DeeplinkRouter.handle(uri) class="c">// 디퍼드 딥링크 라우팅
true
}
}
Adjust.onCreate(config)
registerActivityLifecycleCallbacks(AdjustLifecycleCallbacks())
}
}
2.3 딥링크 라우팅 로직 (디퍼드 포함)
3가지 진입점을 단일 라우터로 통합한다: (a) Universal Link / App Link 진입, (b) 커스텀 URI 스킴, (c) Adjust 디퍼드 딥링크(미설치 상태에서 스토어 경유 후 첫 실행 시 발화). 라우터는 항상 scheme://host/path?params 표준형으로 정규화한 뒤 분기한다.
object DeeplinkRouter {
fun handle(uri: Uri): Boolean {
val path = uri.pathSegments.firstOrNull() ?: return openHome()
return when (path) {
"product" -> openProduct(uri.lastPathSegment ?: return openHome())
"promo" -> openPromo(uri.getQueryParameter("code"))
"invite" -> openReferral(uri.getQueryParameter("ref"))
else -> openHome()
}.also {
Analytics.track("deeplink_opened", mapOf(
"deeplink_path" to uri.path.orEmpty(),
"deeplink_source" to (uri.getQueryParameter("utm_source") ?: "direct"),
"is_deferred" to (DeferredFlag.consume())
))
}
}
}
2.4 표준 URL 스키마
매체 트래커 URL은 Adjust Custom URL에 아래 스키마를 강제한다. 모든 매크로는 매체별 placeholder를 사용하며, 임의 추가는 불가하다.
https://app.adjust.com/{tracker_token}
?campaign={campaign_name}
&adgroup={adgroup_name}
&creative={creative_name}
&idfa={idfa} // iOS 14.5+ 에서는 자동 0 처리
&gps_adid={gps_adid} // Google Play Services ADID
&cost_type=cpc
&cost_amount={cost}
&cost_currency=USD
&deeplink=myapp%3A%2F%2Fproduct%2F{product_id}
&fallback=https%3A%2F%2Fwww.example.com%2Fproduct%2F{product_id}
§3QA 시나리오 (릴리즈 게이트)
아래 12개 시나리오를 모두 PASS해야 LIVE 빌드로 승인된다. 실패 1건 = 빌드 반려.
| ID | Scenario | Expected | Severity | Tool |
|---|---|---|---|---|
| QA-01 | 신규 설치 → 첫 세션 발화 | install + session_start 1회씩 발화 | Blocker | Adjust Testing Console |
| QA-02 | ATT 프롬프트 Authorized 케이스 | IDFA 비공백, attribution sandbox에 도달 | Blocker | Charles Proxy |
| QA-03 | ATT Denied 케이스 | IDFA=00000... 0, SKAN 단일 채널 정상 동작 | Blocker | Console |
| QA-04 | Universal Link 콜드 스타트 | DeeplinkRouter.handle 1회 호출 | Major | Safari → 앱 전환 |
| QA-05 | 디퍼드 딥링크 (미설치 → 설치) | 최초 실행 시 onDeeplinkResponse 발화 | Blocker | Test Console + Test Device |
| QA-06 | 커스텀 URI 스킴 (외부 브라우저) | Router 분기 정확 | Major | adb shell am start |
| QA-07 | 백그라운드 ↔ 포그라운드 전환 | session 중복 발화 없음 | Major | Adjust Logs |
| QA-08 | 비행기 모드 → 온라인 복귀 | 이벤트 큐잉 후 1회만 전송 | Major | Charles |
| QA-09 | SKAN postback (Apple Test Mode) | coarse value [low|medium|high] 매핑 정상 | Blocker | xcrun simctl + Apple Console |
| QA-10 | Cost data 수신 (UAC/AAP) | Adjust 대시보드에 cost_amount 노출 | Minor | Adjust > Cost Data |
| QA-11 | in_app_purchase 매출 이벤트 | revenue + currency 정확 | Blocker | StoreKit Test |
| QA-12 | Uninstall + Reinstall | reattribution 분류 정상 | Major | Adjust > Reattribution |
§4트러블슈팅 / 함정
Adjust.appDidLaunch가 Application과 AppDelegate 양쪽에서 호출되는 경우. Application 클래스에서 단 1회만 호출되도록 통일하고, ActivityLifecycleCallbacks는 register는 하되 SDK 초기화는 분리한다.
1) ATT 프롬프트가 SDK 초기화 이후에 호출되었는지 확인. 2) attConsentWaitingInterval이 너무 짧으면 사용자가 응답하기 전 install이 noIDFA로 굳어버림 — 120초로 설정해 응답 윈도우 확보. 3) LinkMe 도메인이 매체별 클릭 URL에 일치하는지 확인.
Android: Play Install Referrer API 의존성 누락 가능성. com.android.installreferrer:installreferrer:2.2 추가. iOS: Universal Links의 apple-app-site-association가 HTTPS, Content-Type=application/json으로 서빙되는지 검증.
환경 분리는 BuildConfig.DEBUG 또는 #if DEBUG로 빌드 타임에 분기. Sandbox 토큰을 Production에 사용하면 raw event는 Adjust에 도달하지만 attribution은 발생하지 않음.
§5인수인계 체크리스트
QA Lead → Growth PM 인수인계 전 확인:
- Adjust 대시보드 raw export 1일치 다운로드, 디버그 트래픽 분리 검증
- SKAN postback 수신 endpoint 등록 (
skadnetwork.adjust.com) 및 partner re-direct ON - Firebase / Amplitude 등 2nd-party 분석 툴과 user_id 키 정합성 확인
- Adjust → BigQuery / S3 export 파이프라인 sync 확인