블로그 목록
apps

expo-tracking-transparency로 iOS 추적 동의받기 — ATT 프롬프트 없으면 리젝당한다

분석 SDK 하나 붙였다가 앱 심사에서 리젝당했다. 사유는 "앱이 추적 동의를 요청하지 않는다"였다. iOS는 다른 회사 앱·웹사이트에 걸친 사용자 추적을 하려면 반드시 App Tracking Transparency(ATT) 프롬프트를 띄우고 동의를 받아야 한다. 광고 식별자(IDFA)를 쓰거나, 서드파티 광고·어트리뷰션 SDK를 넣었다면 피해갈 수 없다.

프롬프트를 안 띄우면 심사에서 걸리고, 띄워도 거부당하면 IDFA가 전부 0으로 나온다. 둘 다 겪고 나서야 제대로 붙였다.

설치와 설정

npx expo install expo-tracking-transparency

app.json에 플러그인을 등록하면서 사용자에게 보여줄 설명 문구를 넣는다. 이 문구가 비어 있으면 빌드는 되는데 심사에서 또 걸린다.

{
  "expo": {
    "plugins": [
      [
        "expo-tracking-transparency",
        {
          "userTrackingPermissionDescription": "맞춤 광고를 제공하기 위해 추적 권한을 사용합니다."
        }
      ]
    ]
  }
}

권한 요청

중요한 건 타이밍이다. 앱 켜자마자 들이대면 사용자가 반사적으로 거부한다. 서비스 가치를 한 번 보여준 다음, 적절한 화면에서 요청하는 게 동의율이 높다.

import {
  requestTrackingPermissionsAsync,
  getTrackingPermissionsAsync,
} from 'expo-tracking-transparency';

async function ensureTracking() {
  // 이미 물어봤으면 다시 안 물어본다 — 두 번째 호출부터는 그냥 현재 상태만 반환
  const { status, canAskAgain } = await getTrackingPermissionsAsync();

  if (status === 'undetermined' && canAskAgain) {
    const result = await requestTrackingPermissionsAsync();
    return result.status === 'granted';
  }
  return status === 'granted';
}

requestTrackingPermissionsAsync는 처음 한 번만 실제 프롬프트를 띄운다. 거부당한 뒤에는 아무리 호출해도 다시 안 뜨고, 사용자가 설정 앱에서 직접 바꿔야 한다. 그러니 상태에 따라 광고 SDK 초기화를 분기해야 한다.

const granted = await ensureTracking();
if (granted) {
  initAdsWithIDFA();
} else {
  initAdsNonPersonalized(); // IDFA 없이 비개인화 광고로
}

Android는 ATT 개념이 없어서 requestTrackingPermissionsAsync가 항상 granted를 돌려준다. 분기 로직을 플랫폼별로 나눌 필요 없이 그대로 쓰면 된다.

정리

  • 서드파티 추적을 한다면 ATT 프롬프트는 선택이 아니라 필수다.
  • app.json의 userTrackingPermissionDescription을 꼭 채운다. 비면 리젝 사유가 된다.
  • 앱 시작 직후 말고, 맥락이 생긴 화면에서 요청해야 동의율이 산다.
  • 거부/미결정 상태에서도 앱이 멀쩡히 돌아가도록 비개인화 경로를 준비해두자.

Expo Go에서는 네이티브 모듈이라 동작 안 하니, 개발 빌드(dev client)로 확인해야 한다.