J.
BLOG
글 목록소개구독
기술

iOS·Android·Web 광고 노출: loaded·rendered·impression 구분법

2026.08.06·읽기 8분

iOS에서는 광고 SDK의 load 완료를 받고, Android에서는 onAdImpression()을 받으며, Web에서는 DOM에 배너가 붙었는지 확인한다고 해 보자. 세 플랫폼이 모두 ad_impression이라는 이름으로 이벤트를 보내도 같은 현상을 측정한다고 말할 수는 없다.

광고를 내려받은 것, 화면 계층에 붙인 것, viewport와 겹친 것, 광고 SDK가 impression을 기록한 것, 수익 값 callback이 온 것은 서로 다른 사건이다. 이 경계를 합치면 한 플랫폼은 과다 집계되고 다른 플랫폼은 과소 집계된다. 자동 수집 이벤트 위에 같은 수동 이벤트를 얹으면 숫자는 더 조용히 부풀어 오른다.

이 글에서는 광고를 어디에서 가져올지 정하는 라우팅은 다루지 않는다. iOS·Android·Web의 서로 다른 신호를 하나의 측정 계약으로 분류하고, 어떤 신호에서 무엇을 기록하면 안 되는지 정리한다. API 이름은 2026년 8월 6일 Google·Firebase·Web 공식 문서 기준이다.

loaded를 impression으로 세면 숫자가 어긋난다

Google Mobile Ads SDK는 load와 impression을 별도 callback으로 제공한다. Android의 onAdLoaded()는 광고 수신 완료이고 onAdImpression()은 impression이 기록됐을 때 호출된다. iOS 배너도 bannerViewDidReceiveAd(_:)bannerViewDidRecordImpression(_:)을 구분한다.

load 성공은 광고를 표시할 준비가 됐다는 뜻에 가깝다. 아직 화면에 붙지 않았거나, 붙기 전에 화면이 닫히거나, 다른 광고가 슬롯을 차지할 수 있다. 따라서 load callback에서 ad_impression을 보내면 실제 impression 신호보다 앞선 사건을 노출로 바꾸게 된다.

Web도 마찬가지다. DOM에 element를 추가했다고 사용자가 광고를 봤다고 할 수 없다. IntersectionObserver는 target과 root의 교차 비율이 threshold를 넘었는지 알려 준다. 기본 threshold 0은 경계에 닿는 것만으로 callback이 생길 수 있고, observe() 직후에는 보이지 않는 대상에도 첫 callback이 온다. 이것은 기하 신호이지 광고 네트워크의 과금 판정이 아니다.

요청·렌더링·노출·수익 신호를 분리한다

플랫폼 callback을 바로 분석 이벤트 이름으로 바꾸기 전에 다음 단계로 분류하면 경계가 선명해진다.

단계 확인된 사실 아직 확인되지 않은 것
requested 광고 요청이 실제로 시작됨 응답, 렌더링, 노출
loaded 광고 응답 또는 creative를 받음 화면 표시, impression
rendered view 또는 DOM이 화면 계층에 붙음 실제 가시성, SDK impression
app_visible 앱이 정한 viewport 규칙을 만족함 광고 네트워크의 과금 impression
sdk_impression 광고 SDK가 impression을 기록함 수익 값의 존재와 정확도
paid_value SDK가 impression-level 수익 값을 전달함 정산 완료 금액, 항상 정확한 값

이 분류에서 ad_impression으로 올릴 수 있는 가장 강한 신호는 광고 SDK가 제공하는 impression callback이다. SDK가 없는 인하우스 콘텐츠라면 제품이 정한 가시성 규칙을 사용할 수 있지만, evidence=app_visible처럼 근거를 따로 남겨 SDK impression과 같은 품질로 보지 않아야 한다.

paid callback도 새로운 impression을 하나 더 만드는 신호가 아니다. Google의 impression-level ad revenue 값에는 통화와 precision이 포함되며, precision은 unknown이나 estimated일 수 있다. 이 값은 이미 발생한 impression에 연결된 수익 관측값이지 정산 완료를 의미하지 않는다.

공통 이벤트는 이름보다 발화 조건으로 정의한다

이벤트 계약에는 “언제 보낸다”와 함께 “언제 보내지 않는다”를 적어야 한다.

  • ad_load_failed: 실제 요청을 시작한 Provider가 응답을 채우지 못했을 때만 보낸다. 미등록·비활성 Provider를 건너뛴 것은 실패가 아니다.
  • ad_impression: SDK impression callback 또는 문서화한 인하우스 가시성 규칙에서만 보낸다. load 성공이나 Provider 선택만으로 보내지 않는다.
  • 수익 관측: paid callback이 제공한 값·통화·precision을 그대로 보존한다. 값이 없다고 0으로 추정하거나 impression 수로 수익을 역산하지 않는다.
  • 취소: 화면 이탈이나 unmount로 요청을 취소했다면 이후 늦게 도착한 callback을 현재 슬롯의 이벤트로 보내지 않는다.

같은 이벤트 이름을 쓴다는 사실은 계약의 결과일 뿐이다. 발화 조건과 금지 조건이 다르면 이름이 같아도 모집단이 다르다.

자동 수집과 수동 로깅을 한 Provider에 겹치지 않는다

Firebase와 연결된 AdMob은 사용자가 광고 impression을 볼 때 ad_impression을 자동으로 기록할 수 있다. 이 경로가 켜져 있는데 앱이 onAdImpression()에서 같은 GA4 이벤트를 다시 보내면 한 impression이 두 건이 된다.

Provider별 소유권을 한 곳에서 정하면 중복을 막기 쉽다.

Provider 유형 impression의 소유자 앱의 수동 ad_impression
Firebase 자동 수집이 켜진 AdMob SDK와 Firebase 연결 보내지 않음
자동 수집이 없는 광고 SDK SDK impression callback callback에서 1회
인하우스 콘텐츠 앱의 명시적 가시성 규칙 규칙 충족 시 1회
Web DOM만 확인 가능한 외부 콘텐츠 확인 가능한 근거에 따라 별도 정의 네트워크 impression으로 과장하지 않음

자동 이벤트에는 앱이 만든 slot이나 selection_source가 없을 수 있다. 이를 채우겠다고 같은 impression을 수동으로 한 건 더 보내면 총량이 틀어진다. 리포트에서 자동 수집 행의 필드가 비어 있음을 허용하고 evidence별 모집단을 분리하는 편이 낫다.

paid callback을 다른 분석 서버로 전달하는 경우도 마찬가지다. 그 callback을 새 GA4 ad_impression으로 다시 변환하기 전에, Firebase 자동 수집이 이미 같은 impression을 기록하는지 확인해야 한다.

iOS·Android·Web 신호를 같은 분류에 매핑한다

플랫폼 코드는 달라도 분류표는 같게 유지할 수 있다.

플랫폼 신호 공통 분류 ad_impression 발화
iOS bannerViewDidReceiveAd(_:) loaded 아니오
iOS bannerViewDidRecordImpression(_:) sdk_impression 자동 수집이 없다면 예
Android onAdLoaded() loaded 아니오
Android onAdImpression() sdk_impression 자동 수집이 없다면 예
Web element mount rendered 아니오
Web IntersectionObserver 규칙 충족 app_visible 인하우스 규칙에서만 예
iOS paidEventHandler·Android OnPaidEventListener paid_value 새 impression으로 세지 않음

슬롯·시도·광고 인스턴스는 서로 다른 생명주기다

중복 제거를 세션 전체의 Boolean 하나로 처리하면 자동 갱신 광고를 과소 집계한다. 반대로 callback마다 보내면 재시도와 재마운트가 같은 광고를 중복 집계할 수 있다.

slot instance
  ├─ attempt 1 → load failed
  └─ attempt 2 → loaded → impression 1
                            └─ refresh → impression 2

슬롯은 UI가 유지되는 기간이다. attempt는 특정 Provider에 요청한 한 번의 시도다. 광고 인스턴스는 impression을 만들 수 있는 creative의 생명주기다. 실패 중복 제거는 attempt 단위로 하고, impression 중복 제거는 광고 인스턴스 단위로 해야 한다.

이 식별자는 메모리 안에서 callback을 정리하기 위한 값이면 충분하다. GA4에 고유 ID를 그대로 보내 고카디널리티 차원을 만들 필요는 없다. SDK가 새 광고에 대해 impression callback을 보낸 자동 갱신은 새 광고 인스턴스로 세고, 같은 인스턴스의 중복 callback만 제거한다.

이벤트 envelope에는 비교 가능한 문맥만 남긴다

공통 envelope는 작고 닫힌 값으로 유지한다. 다음은 GA4 표준 필드 자체가 아니라, 플랫폼 신호를 분석 시스템에 넘기기 위한 애플리케이션 내부 예시다.

{
  "name": "ad_impression",
  "provider": "global_network",
  "format": "banner",
  "slot": "bottom",
  "evidence": "sdk_impression",
  "selection_source": "normal"
}

provider, format, slot은 플랫폼별 enum을 명시적으로 매핑한다. 새 Provider가 추가됐는데 매핑이 없으면 조용히 임의 문자열을 보내기보다 빌드나 테스트가 실패하게 만든다. evidencesdk_auto, sdk_callback, app_visible처럼 관측 근거를 구분한다.

selection_source는 이벤트의 주어에 맞춰 계산해야 한다. 강제 선택한 Provider가 실패하고 다른 광고가 표시됐다면, 실패 이벤트의 source와 최종 impression의 source는 같지 않을 수 있다. 한 번 계산한 값을 체인 전체에 복사하면 실패 원인과 실제 노출이 함께 오염된다.

수익 값은 이 envelope에 억지로 기본값을 넣지 않는다. Firebase ad_impression에 value를 제공한다면 currency도 함께 제공해야 하고, SDK가 준 precision은 별도 수익 파이프라인에서 보존한다. 자동 수집 행과 수동 행의 필드 완성도가 다르다는 사실도 계약에 포함한다.

실패 사례로 이벤트 계약을 검증한다

실제 광고 재고를 기다리는 테스트보다 callback을 주입한 작은 표가 더 안정적이다.

입력 기대 이벤트
load 성공만 도착 impression 0건
SDK impression callback 1회 impression 1건
같은 광고 인스턴스 callback 중복 impression 1건
새 광고로 refresh 후 impression callback impression 누적 2건
Firebase 자동 수집 대상 Provider 수동 impression 0건
요청 전에 Provider를 건너뜀 load failure 0건
요청 실패 후 다른 Provider가 표시됨 실패 1건, impression 1건, 각 source 별도 계산
unmount 뒤 늦은 callback 이벤트 0건
Web observer의 보이지 않는 첫 callback impression 0건
독립된 슬롯 두 곳에서 각각 표시 impression 2건

Web 가시성 규칙을 쓴다면 threshold뿐 아니라 유지 시간과 이탈 시 reset 조건도 테스트해야 한다. IntersectionObserver entry 하나는 특정 순간만 나타내므로 callback이 왔다는 사실만으로 노출 시간을 만들 수 없다. trackVisibility도 제한적이고 실험적인 기능이므로 필수 전제로 두지 않는다.

정리

광고 노출 이벤트를 하나의 언어로 만든다는 것은 모든 callback을 ad_impression으로 바꾸는 일이 아니다. loaded, rendered, 앱 가시성, SDK impression, paid value를 먼저 분리하고, 각 플랫폼에서 가장 강한 근거만 공통 계약에 올리는 일이다.

자동 수집과 수동 로깅의 소유권을 Provider별로 하나만 정하고, 실패는 attempt 단위로, impression은 광고 인스턴스 단위로 중복 제거해야 한다. 그러면 iOS·Android·Web의 구현이 달라도 대시보드의 숫자가 무엇을 뜻하는지는 같게 유지할 수 있다.

출처

광고Coupang Partners

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

#iOS#Android#Web#Advertising#Analytics
이전 글Game Center·Play Games 리더보드: 날짜 경계·제출 신뢰성
© 2026 진재명 · blog.jaemyeong.com
iOS 소프트웨어 엔지니어 · 부산