포스트

무효 트래픽으로 광고 계정이 29일 정지됐습니다 — 범인은 제 개발용 폰이었습니다

8월 26일 광고 게시자 계정이 무효 트래픽으로 29일 정지됐습니다. 이의신청 창구도 없었습니다. 전 코드베이스를 감사해 찾은 원인은 악의적 클릭이 아니라, 실제 광고 ID가 들어간 빌드를 개발용 폰에 깔고 계속 실행한 것이었습니다. 원인 규명 과정, 코드 네 곳에 넣은 차단 조치, 그리고 가장 뼈아팠던 사실

무효 트래픽으로 광고 계정이 29일 정지됐습니다 — 범인은 제 개발용 폰이었습니다

8월 26일, 광고 게시자 계정이 정지됐습니다. 사유는 무효 트래픽이고 기간은 29일입니다. 어제로 그 29일이 지났습니다.

정지 기간에는 광고가 아예 나가지 않습니다. 수익이 0이 되는 것보다 더 답답한 건 두 가지였습니다. 첫째, 이의신청 창구가 없었습니다. 소명해서 풀 수 있는 종류가 아니라 기간이 지나야 자동으로 풀리는 조치였습니다. 둘째, 정지 기간에는 콘솔 접근 자체가 막힙니다. 테스트 기기를 등록해 재발을 막고 싶어도 그 화면에 들어갈 수 없었습니다.

그리고 안내에는 이렇게 적혀 있었습니다. 재발하면 영구 정지이고, 최대 60일치 수익이 몰수될 수 있습니다. 그래서 29일을 기다리는 것보다 원인을 정확히 찾는 게 급했습니다.

무효 트래픽이 무슨 뜻인지 오해하고 있었다

“무효 트래픽”이라는 말을 처음 보고 저는 누가 제 앱의 광고를 반복 클릭하는 공격을 떠올렸습니다. 그래서 처음 며칠은 “누가 그랬을까”를 찾았습니다. 방향이 틀렸습니다.

무효 트래픽은 광고주에게 가치가 없는 노출과 클릭 전부를 뜻합니다. 악의가 있어야 성립하는 개념이 아닙니다. 개발자가 자기 앱을 테스트하면서 만든 노출도 여기에 들어갑니다. 광고주는 앱을 만든 사람에게 광고를 보여 주려고 돈을 낸 게 아니니까요.

이 정의를 제대로 읽고 나서야 제 코드를 의심하기 시작했습니다.

코드베이스 네 개를 전부 감사했다

당시 광고가 들어간 앱은 네 개의 코드베이스에 흩어져 있었습니다. Flutter 앱 두 개, 안드로이드 래퍼 하나, 별도 안드로이드 앱 하나입니다. 전부 열어서 두 가지만 확인했습니다.

첫째, 디버그 빌드가 어떤 광고 ID를 쓰는가.

Flutter 앱 두 개는 디버그 빌드에서도 실제 광고 ID를 그대로 하드코딩하고 있었습니다. 개발 중에 폰에 깔고 실행할 때마다 진짜 광고가 나가고, 그 노출이 전부 제 계정에 기록됐다는 뜻입니다.

둘째, 테스트 기기 등록 코드가 있는가.

setTestDeviceIds를 검색했습니다. 네 개 코드베이스에서 0건이었습니다. 이 설정이 있으면 지정한 기기에서 나가는 요청이 테스트 요청으로 처리돼 무효 트래픽으로 잡히지 않습니다. 그게 어디에도 없었습니다.

두 가지를 합치면 결론이 나옵니다. 제 개발용 폰에 실제 광고 ID가 들어간 빌드를 깔아 두고 몇 달 동안 계속 실행한 것이 유력한 원인입니다. 가족 폰에 사이드로드해 둔 빌드도 같은 성격이었습니다.

구글이 정확한 원인을 알려 주지는 않으므로 이건 추정입니다. 다만 다른 설명이 필요 없을 만큼 명백한 구멍이었습니다.

부차 원인: 내부 테스트 트랙

하나 더 찾았습니다. Play 내부 테스트 트랙에 제 이메일과 아내 이메일이 테스터로 등록돼 있었습니다.

여기서 놓치기 쉬운 점이 있습니다. 내부 테스트 트랙의 빌드는 릴리즈 서명입니다. 즉 디버그 빌드가 아니라 실제 광고 ID로 동작하는 빌드입니다. “테스트 트랙이니까 테스트로 처리되겠지”라고 생각하기 쉬운데, 광고 쪽에서 보면 그냥 실제 사용자입니다.

의심했지만 원인이 아니었던 것

개발 PC에 안드로이드 에뮬레이터 두 대가 있습니다. 자동 플레이를 돌려 본 적이 있어서 이쪽을 강하게 의심했습니다.

확인해 보니 무관했습니다. AdMob은 에뮬레이터를 자동으로 테스트 기기로 취급합니다. 에뮬레이터에서 앱을 실행하고 로그를 보니 “이 요청은 테스트 기기에서 전송되었습니다”라는 메시지가 그대로 찍혔습니다. 별도 설정 없이도 무효 처리된다는 뜻입니다.

다만 점검하다가 다른 걸 발견했습니다. 그 에뮬레이터에 실제 광고 ID를 쓰는 릴리즈 빌드가 몇 개 설치돼 있었습니다. 자동 테스트 기기라 결과적으로 무해했지만, 우연에 기댄 안전입니다. 앞으로 에뮬레이터에도 디버그 빌드만 올리기로 정했습니다.

넣은 조치

1. 빌드 종류로 광고 ID를 가른다

디버그 빌드는 구글이 제공하는 테스트 광고 유닛만 쓰도록 분기했습니다. Flutter 쪽은 kReleaseMode로 갈랐고, 디버그 매니페스트에서 애플리케이션 ID까지 샘플 값으로 덮어썼습니다. 사람이 기억해서 바꾸는 방식은 반드시 한 번은 실패합니다.

2. 테스트 기기를 코드에 등록한다

개발용 폰, 태블릿, 가족 폰 세 대의 기기 해시를 확보해 네 개 코드베이스 전부에 setTestDeviceIds로 넣었습니다.

기기 해시를 뽑는 방법이 조금 번거로웠습니다. 앱을 실행하면 로그에 “테스트 기기로 등록하려면 이 해시를 쓰세요”라는 안내가 찍히는데, 그 로그를 보려면 앱을 한 번 실행해야 합니다. 그 실행 자체가 무효 트래픽이 되면 안 되니 반드시 디버그 빌드로 해야 합니다. 원격 폰은 무선 디버깅으로 붙어서 디버그 APK를 설치하고, 로그를 뽑은 뒤 지웠습니다.

3. 테스터와 트랙을 정리한다

Play의 테스터 이메일 목록을 전부 비웠고, 가족 폰의 사이드로드 APK를 제거했고, 내부 테스트 트랙 열한 개를 전부 일시중지했습니다. Play는 업로드한 번들을 삭제하는 기능이 없어서, 일시중지 + 테스터 0명이 도달할 수 있는 최종 상태입니다.

4. 배너가 겹치던 것을 고쳤다

감사 중에 별개 문제를 발견했습니다. 안드로이드에서 하단 배너가 콘텐츠 위로 겹쳐 그려지고 있었습니다. 화면 아래쪽 버튼을 누르려다 배너를 누르는 일이 생길 수 있는 배치입니다. 우발적 클릭도 무효 트래픽입니다. 배너가 로드되면 그만큼 아래 여백을 확보하도록 바꿨습니다.

한 겹 더: 감지되면 실제 광고를 요청하지 않는다

위 조치는 전부 “제가 올바르게 설정했다면”을 전제로 합니다. 그래서 코드 쪽에 마지막 안전장치를 하나 더 넣었습니다.

에뮬레이터, 루팅된 기기, 디버깅 가능 상태 중 하나라도 감지되면 릴리즈 빌드에서도 구글 샘플 광고 유닛만 요청합니다. 기기 정보를 위조하는 환경까지 염두에 둔 처방입니다. 정상적인 사용자 기기에서는 아무 영향이 없고, 개발 환경에서 실수로 릴리즈 빌드를 돌려도 실제 광고가 나가지 않습니다.

가장 뼈아팠던 사실

여기까지 8월 말에 마무리했습니다. 그런데 계산에서 빠뜨린 게 있었습니다.

코드 수정은 배포되기 전까지 라이브 앱에 아무 효력이 없습니다.

제 PC의 코드는 안전해졌지만, 사용자 폰에 깔려 있는 앱은 여전히 옛 코드입니다. 정지가 풀려 광고가 다시 나가기 시작하는 시점에 보호받으려면 그 전에 수정 코드가 실제로 배포돼 있어야 합니다. 이 당연한 사실을 정지 해제일을 앞두고서야 셈에 넣었고, 9월 초에 광고가 들어간 앱들을 부랴부랴 업데이트해 올렸습니다.

남긴 수칙

같은 일을 겪지 않기 위해 규칙으로 적어 두었습니다.

  1. 디버그 빌드는 테스트 광고 유닛만 쓴다. 사람 기억이 아니라 빌드 설정으로 강제한다.
  2. 실기기를 테스트 기기로 등록한다. 개발자 본인 기기, 가족 기기 전부.
  3. 릴리즈 빌드를 실기기에서 광고와 함께 실행하지 않는다. 확인이 필요하면 디버그 빌드로 한다.
  4. 가족·지인 폰에 사이드로드하지 않는다. 테스트 트랙 테스터로도 넣지 않는다.
  5. 테스트 트랙은 쓰지 않을 때 일시중지한다. 릴리즈 서명 = 실제 광고다.
  6. 배너가 조작 영역과 겹치지 않게 한다. 우발적 클릭도 무효 트래픽이다.
  7. 새 광고 앱을 추가할 때 위 설정을 복사해 넣는다. 새로 만든 앱이 가장 위험하다.

그리고 가장 중요한 인식 하나를 바로잡았습니다. 테스트 기기 등록은 그 기기의 노출과 클릭을 무효로 처리해 주는 장치일 뿐, 반복 클릭에 대한 면죄부가 아닙니다. 등록해 뒀다고 마음 놓고 광고를 눌러 보는 것은 여전히 위험합니다.

남은 이야기

같은 시기에 앱인토스 쪽에서도 광고 어뷰징 정책이 강화됐습니다. 9월 정책 정리 글에 적었듯이 “비정상적으로 늘어난 광고 노출이 정상적으로 운영하는 미니앱의 기회와 수익을 줄여왔다”는 것이 플랫폼의 문제 인식입니다. 제 사고는 규모가 작고 악의도 없었지만, 그 문장이 가리키는 방향에 제가 있었다는 것은 부정할 수 없습니다.

한 달간 Play 쪽 수익이 0이 되면서 앱인토스 광고 수익만 공개하게 된 것도 이 사고 때문입니다. 그쪽 숫자는 다음 공개 때 함께 넣겠습니다.

광고를 붙일 때 가장 먼저 해야 하는 일이 광고를 예쁘게 배치하는 것이 아니라 내 기기에서 나가는 요청을 차단하는 것이라는 걸, 한 달을 잃고 배웠습니다.

만든 앱들은 Google Play와 앱인토스에 있고, 일부는 브라우저에서 바로 해볼 수 있습니다. 소식은 이 블로그와 인스타그램(@fadongkwon.soft) (새 창에서 열림)에서 전해드립니다.

이 글은 제 계정에서 실제로 일어난 일과 그에 대한 제 추정을 정리한 것입니다. 무효 트래픽 판정의 정확한 근거는 플랫폼이 공개하지 않으므로, 같은 증상의 원인이 늘 같다고 볼 수는 없습니다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.