애드몹 UMP 동의 게이트 적용하는 법 — 동의 없이 광고가 나가면 벌어지는 일

동의가 확인되기 전 광고 요청 0건 — 차단 9층 구조

앱에 애드몹을 붙일 때 흔한 순서가 이렇습니다. SDK를 켜고, 광고를 요청하고, 잘 뜨는지 확인하고, 그다음에 "아 동의 창도 넣어야 하지"를 떠올립니다.

그 순서는 위험합니다. 관련 지역 사용자에게 필요한 동의 절차를 거치지 않은 채 광고 요청이 발생하면 정책 위반 위험이 있습니다. 정책 집행은 사안별로 다르지만 계정 단위로 영향을 받을 가능성도 있으므로, 앱 하나의 화면 문제가 아니라 운영 계정 전체의 안전장치로 다뤄야 합니다.

이 글은 제 앱에 들어 있는 동의 게이트 코드를 그대로 꺼내 정리한 겁니다. 핵심은 하나예요 — 기본값을 "광고 요청 불가"로 두는 것.

📌 3줄 결론
- 동의 상태를 못 읽었을 때 기본값이 "허용"이면 안 됩니다. 조회 실패도 거부와 똑같이 취급해야 합니다
- 동의 확인은 광고 요청 앞이 아니라 SDK 초기화 앞에 와야 합니다
- 웹용 광고 코드와 앱을 한 저장소에서 쓴다면, 웹 광고가 앱 안에서 뜨지 않게 막는 장치가 따로 필요합니다

확인일 2026-08-14 · 대상: 제가 직접 운영하는 안드로이드·iOS 앱 저장소

작성·검증 방식
ARION 운영자가 실제 저장소의 동의 게이트·광고 초기화 순서·웹 광고 차단 검사를 읽고 대조했습니다. AI는 구조와 표현 정리에만 보조로 사용했고, 코드 줄 수와 관찰값은 비밀값을 제거한 로컬 증거 파일 admob-ump-consent-2026-08-14.txt와 Google 공식 문서로 다시 확인했습니다.

검증한 범위와 아직 남은 범위

범위 2026-08-14 확인 결과 판정
동의 변수 초기값 요청 불가·비맞춤으로 시작 코드 확인 완료
조회 실패 처리 catch에서도 안전한 값으로 복귀 코드 확인 완료
초기화 순서 동의 확인이 SDK 초기화보다 앞 코드 확인 완료
웹 광고 차단 9개 판정층과 호스트 허용 목록 확인 정적 검사 완료
동의·거부·조회실패 3경로 실기기 테스트 광고 실행 전 미검증

그래서 이 글은 “정책 위반이 절대 없다”는 인증서가 아닙니다. 코드가 동의 전 요청을 막도록 설계됐다는 정적 증거와, 아직 실기기에서 확인하지 못한 범위를 분리한 기록입니다.

기본값이 "안 됨"이어야 하는 이유

UMP는 구글이 제공하는 동의 관리 도구입니다. 유럽 경제 지역과 영국 사용자에게 광고를 보여주려면 동의를 받아야 하고, UMP가 그 창을 띄우고 결과를 알려줍니다.

문제는 "결과를 알려주지 못하는 경우" 입니다. 네트워크가 끊겼거나, 조회가 실패했거나, 응답 형식이 예상과 다를 때요. 이때 코드가 "일단 광고 띄우자"로 흐르면 그게 사고입니다.

제 앱은 이렇게 시작합니다.

// 🛡️ UMP(GDPR) 동의 게이트 — 기본 '요청 불가 + 비맞춤'(fail-closed).
//   동의 확인 전·조회 실패 시에는 어떤 광고 요청도 나가지 않는다.
let _canRequestAds = false;
let _npa = true;

두 변수의 초기값이 전부입니다. _canRequestAds 가 거짓이고 _npa(비맞춤 광고)가 참이에요. 아무 일도 안 일어난 상태가 가장 안전한 상태가 되도록 맞춰둔 겁니다.

그리고 조회에 실패했을 때도 같은 값으로 되돌립니다.

}catch(e){
  _canRequestAds = false;
  _npa = true;
  _privacyOptionsRequired = false;
  _updateAdPrivacyEntry();
  return null;
}

catch 블록이 비어 있거나 그냥 로그만 찍고 넘어가면, 실패 이전의 값이 그대로 남습니다. 그게 참이었다면 광고가 나갑니다. 예외 처리에서 안전한 값으로 되돌리는 것까지가 게이트입니다.

순서가 광고 요청보다 앞이다

많이 놓치는 자리입니다. 동의 확인을 "광고 요청 직전"에 두면 늦어요. SDK 초기화보다 앞이어야 합니다.

제 앱의 광고 초기화 함수는 이렇게 시작합니다.

// ⓪ GDPR/UMP 동의 먼저 — canRequestAds 확정 전에는
//    SDK 초기화·ATT·광고 요청을 하지 않는다(fail-closed).
let info = await _refreshConsent(AdMob);
if (!_canRequestAds && info && info.isConsentFormAvailable) {
  await AdMob.showConsentForm().catch(() => {});
  await _refreshConsent(AdMob);
}
if (!_canRequestAds) {
  _admobInit = false;   // 복귀 시 재시도 가능
  ...
}

읽어보면 흐름이 보입니다. 동의를 조회하고, 아직 안 됐는데 폼을 띄울 수 있으면 띄우고, 다시 조회합니다. 그래도 안 되면 거기서 멈춥니다.

마지막 줄이 중요해요. _admobInit = false 로 되돌립니다. 이걸 안 하면 "초기화 시도했음" 상태로 굳어서, 나중에 사용자가 동의해도 다시 시도하지 않습니다. 그래서 앱이 다시 활성화될 때 재시도하도록 이벤트도 걸어둡니다.

그리고 동의를 못 받아도 앱의 본래 기능은 그대로 돌아갑니다. 광고만 안 나갈 뿐이에요. 이게 게이트를 만들 때의 기준입니다 — 광고가 막힌다고 제품이 망가지면 안 됩니다.

재진입 경로가 있어야 한다

동의는 한 번 받고 끝이 아닙니다. 사용자가 마음을 바꿀 수 있어야 하고, 그러려면 설정 화면에 들어가는 문이 있어야 해요.

다만 모든 지역에 보여주면 안 됩니다. 동의가 필요 없는 지역에서 "광고 개인정보 설정"이 뜨면 사용자만 혼란스럽습니다. UMP가 그 판단 값을 줍니다.

_privacyOptionsRequired =
  String(info?.privacyOptionsRequirementStatus ?? '').toUpperCase() === 'REQUIRED';

이 값이 REQUIRED 인 지역에서만 설정 항목을 노출합니다.

웹 광고가 앱 안에서 뜨면 안 되는 이유

여기가 이 글에서 가장 특이한 부분일 겁니다.

제 앱은 웹뷰 기반이라 같은 소스가 웹사이트로도 나가고 앱으로도 나갑니다. 웹에서는 애드센스를, 앱에서는 애드몹을 씁니다. 그런데 애드몹과 애드센스가 같은 발행자 ID를 공유합니다.

그래서 앱 안에서 애드센스 코드가 로드되면 정책 위반 소지가 커지고, 집행 결과가 연결된 광고 계정에 영향을 줄 가능성도 있습니다. 앱 하나의 화면이 아니라 계정 운영 차원에서 차단해야 하는 문제예요.

막는 코드를 따로 뒀습니다. 9층입니다.

L1 capacitor-native     네이티브 플랫폼 판정이 참
L2 capacitor-present    Capacitor 객체 존재 자체 — 웹에는 없어야 하는 물건
L4 capacitor-prefix     'Capacitor' 접두 전역 스윕(미래 추가분까지)
L5 wk-message-handler   iOS 웹뷰의 브릿지 메시지 핸들러
L6 protocol             http:/https: 가 아님(capacitor:·file:·ionic:)
L8 host-not-allowed     hostname 이 배포 허용 목록 밖 ← 마지막 안전벨트

한 층이 뚫려도 다음 층이 잡도록 겹쳐 놨습니다. 그리고 마지막 층에 함정이 하나 숨어 있어요.

Capacitor 안드로이드는 앱 내부를 https://localhost 로 서빙합니다. 그래서 허용 호스트 목록에 localhost 를 무심코 넣으면 앞의 여덟 층을 다 통과한 뒤 마지막 층에서 그대로 열립니다. 그 한 줄이 계정을 날릴 수 있어요. 코드에 이렇게 적어뒀습니다.

// localhost 는 의도적으로 넣지 않는다 — Capacitor Android WebView 가 https://localhost 다.

판정을 한 곳에 모은다

광고를 언제 보여줄지 정하는 규칙 — 첫 실행 보호, 최소 간격, 하루 상한 같은 것들 — 은 화면마다 흩어지기 쉽습니다. 흩어지면 화면에 따라 다르게 동작하고, 그러면 어느 쪽이 맞는지 아무도 모르게 됩니다.

한 파일로 모아두고 검사를 붙였습니다. 줄 수를 세보면 이렇습니다.

ad-policy.js         236 줄
test-ad-policy.js    799 줄

검사가 본체의 3.4배입니다. 광고 판정은 눈으로 확인하기 가장 어려운 종류예요. 첫 실행 보호가 깨졌는지, 하루 상한이 안 먹는지는 며칠 써봐야 드러나고, 그때는 이미 정책 위반이 쌓인 뒤입니다.

직접 확인하는 법

내 앱이 fail-closed 인지 몇 분이면 확인합니다.

동의 관련 변수의 초기값을 봅니다. 참으로 시작하면 그 자체가 구멍입니다.

git grep -nE "canRequestAds|npa|consentGranted" -- "*.js" "*.ts" "*.kt"

예외 처리에서 안전한 값으로 되돌리는지 확인합니다.

git grep -n -A5 "catch" 광고초기화파일 | grep -iE "canRequestAds|npa"

동의 확인이 SDK 초기화보다 앞에 있는지, 함수를 열어 순서를 눈으로 봅니다. 이건 검색으로 못 잡고 읽어야 합니다.

웹 광고 코드가 함께 있다면 앱에서 막히는지 확인합니다.

git grep -nE "Capacitor|isNativePlatform|location.protocol" 웹광고파일

광고·앱 배포 운영을 같이 점검하기

자주 묻는 질문 (FAQ)

Q. 한국 앱인데 유럽 사용자가 없으면 안 넣어도 되나요?
스토어에 올라간 앱은 지역을 완전히 막지 않는 한 어디서든 설치됩니다. 그리고 UMP는 필요 없는 지역에서 NOT_REQUIRED 를 돌려주므로 넣어도 손해가 없습니다.

Q. 동의 창이 안 뜨는데 정상인가요?
필요 없는 지역이면 안 뜨는 게 맞습니다. 다만 그 경우에도 canRequestAds 가 참으로 확정돼야 광고가 나갑니다. 조회 자체가 실패한 것과 구분해야 해요.

Q. 광고가 아예 안 나오는데요?
fail-closed 설계라면 그게 의도된 동작일 수 있습니다. 동의 상태를 로그로 찍어 NOT_REQUIRED 인지 UNKNOWN 인지 먼저 확인하세요.

Q. 이 코드로 정책 위반이 없다고 보장되나요?
아니요. 코드가 보장하는 건 "동의 확인 전에 요청이 나가지 않는다"까지입니다. 실제 광고가 어떻게 표시되는지, 빈도 상한이 실기기에서 지켜지는지는 별개예요.

Q. 실제로 검증했나요?
코드까지만입니다. 저는 아직 실기기에서 테스트 광고로 동의 3경로(동의·거부·조회실패)를 돌려보지 않았습니다. 그건 코드로 증명할 수 없는 영역이고, 지금 이 글의 한계입니다. 하게 되면 결과를 따로 적겠습니다.

실행 체크리스트

  • 동의 관련 변수의 초기값을 거부·비맞춤으로 둔다
  • catch 에서도 같은 안전한 값으로 되돌린다
  • 동의 확인을 SDK 초기화보다 앞에 배치한다
  • 동의 미확정이면 초기화 플래그를 되돌려 나중에 재시도할 수 있게 한다
  • 동의를 못 받아도 앱의 본래 기능은 돌아가게 한다
  • privacyOptionsRequirementStatus 가 필요한 지역에서만 설정 진입점을 노출한다
  • 웹 광고 코드가 같은 저장소에 있으면 앱에서 뜨지 않게 따로 막는다
  • 허용 호스트 목록에 localhost 를 넣지 않는다
  • 광고 판정을 한 파일로 모으고 검사를 붙인다
  • 실기기 테스트 광고로 동의 3경로를 직접 돌려본다 — 코드만으로는 끝나지 않는다

참고한 공식 문서

다음 글 예고

다음은 하드디스크가 죽기 전에 보내는 신호입니다. 어느 날 버스에서 통째로 사라진 2TB 디스크의 실제 상태값을 윈도우에서 읽는 방법으로 정리하겠습니다.

참고 자료

출처 확인일: 2026-08-14

댓글

이 블로그의 인기 게시물

일 잘하는 사람의 AI 도구 5개 — 업무 시간 반으로 (2026)

옛날 사진 AI로 복원하는 법 — 부모님 흑백사진 살리기 (2026)

AI로 자기소개서·이력서 첨삭받는 법