들어가며
iOS·Safari·Firefox·브라우저 확장이 합세해 클라이언트 측 광고 픽셀의 30~50%를 차단합니다. 사용자가 광고를 보고 사이트에 들어와 구매했는데, 그 구매 이벤트가 광고 플랫폼에 도달하지 못해 ROAS 보고서에 빠지는 일이 매일 일어납니다. Server-side tagging과 각 광고 플랫폼의 Conversion API(CAPI)는 이 데이터 손실을 메우는 표준 답안이 되었습니다. 이 글은 마케터가 서버 측 태깅의 구조와 운영 함정을 이해하고, 자기 회사 측정 인프라를 점검할 수 있도록 코드·아키텍처·운영 관점을 한 글에서 정리합니다.

클라이언트 픽셀이 차단된 영역에서도 서버는 이벤트를 보낸다
클라이언트 vs 서버 — 무엇이 다른가
전통적인 마케팅 픽셀(Meta Pixel·GA4·TikTok Pixel)은 사용자의 브라우저에서 직접 광고 플랫폼으로 HTTP 요청을 보냅니다. 이 구조는 단순하지만 다음 영역에서 깨집니다.
📌 이 글의 전제
독자가 GTM·픽셀·전환 추적이라는 단어는 일상에서 쓰고, ROAS·CAC 같은 지표를 매주 본다고 가정합니다. 백엔드 코드를 직접 짠 적은 없어도 무방합니다.
클라이언트 측 픽셀이 깨지는 자리
- 광고 차단기 — uBlock Origin 등이 도메인을 차단해 요청 자체가 안 떠남
- iOS Safari ITP — 1st-party 쿠키 7일 만료, 3rd-party 쿠키 차단
- 네트워크 손실 — 사용자가 페이지를 빠르게 떠나면 픽셀 호출이 완성되기 전 끊김
- 브라우저 확장 — Privacy Badger·Ghostery 등이 추적 스크립트 차단
이 손실의 합이 채널마다 20~50%까지 빠진다는 게 업계 보편 관찰입니다.
서버 측 태깅이 해결하는 것
서버 측 태깅은 웹사이트 서버나 자체 GTM 서버 컨테이너에서 광고 플랫폼으로 직접 HTTP 요청을 보냅니다. 사용자의 브라우저가 그 요청에 관여하지 않으니, 클라이언트 차단기·확장·네트워크 끊김의 영향을 받지 않습니다.
다만 매칭의 부담이 늘어납니다 — 서버에서는 누가 광고를 봤는지 알 수 없으니, 이벤트와 광고 노출을 잇는 매칭 키(이메일·전화번호·user agent·IP)를 함께 보내야 합니다. 이 매칭 키의 품질이 측정 정확도의 절반입니다. 더 넓은 1st-party 데이터 구조를 이해하려면 CDP ID 그래프 글을 참조하면 됩니다.
광고 플랫폼별 CAPI 표준
각 광고 플랫폼이 비슷한 구조의 서버 측 API를 제공합니다.
Meta CAPI
| 필드 | 의미 |
|---|---|
| event_name | Purchase·Lead·AddToCart 등 |
| event_time | UNIX timestamp |
| user_data | 해싱된 이메일·전화·이름·IP·user agent |
| custom_data | 매출·통화·콘텐츠 ID |
| event_source_url | 이벤트가 발생한 페이지 |
| action_source | website·app·offline 등 |
user_data 필드의 매칭 키 개수와 정확도가 EMQ(Event Match Quality) 점수를 결정하고, 점수가 7점 이상이어야 광고 최적화에 신호가 잘 들어갑니다.
GA4 Measurement Protocol
GA4는 같은 측정 ID로 클라이언트(gtag.js)와 서버 모두에서 이벤트를 보낼 수 있는 Measurement Protocol을 제공합니다. 서버 이벤트가 클라이언트 이벤트와 같은 client_id를 공유하면 한 사용자의 두 출처 데이터가 자동 합쳐집니다. GA4 데이터를 BigQuery로 내려받아 ROAS 계산에 활용하는 패턴은 GA4 BigQuery ROAS 글에서 다룹니다.
TikTok Events API·Google Ads Enhanced Conversions
TikTok·Google도 같은 패턴의 API를 제공합니다. 이름은 다르지만 구조는 동일 — 해싱된 사용자 신호 + 이벤트 + 컨텍스트.
매칭 키와 해싱 — 정확도의 절반
서버 측 이벤트는 광고 플랫폼이 자기 데이터의 어떤 사용자와 매칭할지 모릅니다. 그래서 보내는 사용자 신호의 품질이 곧 측정 정확도입니다.
표준 처리 절차와 정규화 코드
서버에서 매칭 키를 보낼 때 표준은 SHA-256 해시입니다. 하지만 단순 해싱이 아니라 다음 정규화가 선행되어야 합니다.
- 이메일 → 전체 소문자, 공백 제거 → SHA-256
- 전화번호 → 국가코드 포함 E.164 형식(
+82101234...) → 숫자만 → SHA-256 - 이름 → 소문자, 공백 제거 → SHA-256
이 정규화 단계 하나가 빠지면 매칭률이 30~50% 떨어집니다. 이메일이 "User@Gmail.com"으로 들어와 그대로 해싱되면 같은 사람이 광고 플랫폼에서 등록한 "user@gmail.com"과 매칭이 안 됩니다. 아래 코드가 그 정규화 + SHA-256 파이프라인 전체입니다.
"test_event_code": "TEST12345", # 테스트 시에만; 운영에서는 제거
}
resp = requests.post(CAPI_URL, json=payload, timeout=5)
resp.raise_for_status()
return resp.json() # {"events_received": 1, "fbtrace_id": "..."}
💡 event_id 생성 규칙
클라이언트 픽셀에서
fbq('track', 'Purchase', data, {eventID: 'order_123'})형태로 보낼 때, 서버에서도 동일한'order_123'을 event_id로 써야 Meta가 중복을 잡습니다. UUID를 쓸 경우, 주문 확인 페이지가 렌더링될 때 생성해 세션에 저장하고 서버에도 전달합니다.
아키텍처 옵션 — 어디에 서버 컨테이너를 둘 것인가
옵션 A — 자체 백엔드 직접 호출
웹사이트의 백엔드(예: Next.js API route, Django view)에서 구매가 일어날 때 곧장 Meta CAPI에 HTTP 호출을 보냅니다. 가장 단순하지만, 광고 플랫폼 추가 시마다 백엔드 코드를 또 만지게 됩니다.
옵션 B — Server-side GTM(sGTM)
GTM 서버 컨테이너를 별도 도메인(예: gtm.example.com)에 띄우고, 클라이언트가 그쪽으로 이벤트를 보내면 sGTM이 표준화한 뒤 여러 광고 플랫폼에 동시 전송합니다. 운영 도구가 GUI라 마케터·분석가가 광고 플랫폼을 추가·제거할 수 있습니다.
sGTM 컨테이너가 받는 요청은 내부적으로 다음 흐름으로 처리됩니다.
# sGTM fetch handler 의사코드 (Cloud Run 환경 기준)
클라이언트 → gtm.example.com/collect?... (GA4 형식 이벤트)
↓
sGTM 컨테이너 수신
1. Request 파싱 (event_name, client_id, user_data 추출)
2. 변환 태그 실행:
- GA4 서버 태그 → 구글 수집 서버로 전달
- Meta CAPI 태그 → CAPI 엔드포인트 호출
- TikTok Events API 태그 → TikTok 엔드포인트 호출
3. 응답 반환 (200 OK)
↓
각 플랫폼 Ads 서버
옵션 C — CDP·Reverse-ETL 도구
Segment·RudderStack·Hightouch 같은 도구가 데이터 웨어하우스의 이벤트를 광고 플랫폼으로 보냅니다. 이미 dbt·Snowflake가 깔린 조직에 적합합니다.
| 옵션 | 운영 주체 | 장점 | 단점 |
|---|---|---|---|
| 자체 백엔드 | 엔지니어 | 통제 최대 | 변경마다 코드 |
| sGTM | 마케터·분석가 | GUI·다플랫폼 | 호스팅 비용 |
| CDP·rETL | 데이터팀 | 웨어하우스 통합 | 도구 라이선스 |
조직의 데이터 성숙도에 맞는 옵션을 고르는 게 정답입니다. 회사가 dbt·Snowflake가 이미 있으면 옵션 C가 자연스럽고, 마케팅팀이 직접 운영하고 싶으면 옵션 B가 좋습니다.
{/* TODO_HUNY: 우리 회사가 현재 사용하는 옵션(A·B·C)과 운영하면서 마주친 가장 큰 함정 한 단락 */}
정규화 파이프라인 — PII 해싱 + BigQuery 적재
대량의 오프라인 전환(CRM, 오프라인 구매)을 처리할 때는 Python 파이프라인으로 PII를 해싱하고 BigQuery에 적재해두는 방식이 표준입니다. 아래는 10줄 안에서 핵심 흐름을 표현한 코드입니다.
python
from google.cloud import bigquery
Top comments (0)