기술 포스트 · Analytics Ops (GA4·GTM)
Server-side Tagging과 Conversion API — 1st-party 데이터를 직접 운영하는 법
브라우저에서 보내는 픽셀이 30~50% 차단되는 시대에, 서버에서 광고 플랫폼으로 직접 이벤트를 보내는 server-side tagging과 Meta CAPI·GA4 Measurement Protocol·TikTok Events API의 핵심을 마케터 시선에서 정리합니다.
들어가며
iOS·Safari·Firefox·브라우저 확장이 합세해 클라이언트 측 광고 픽셀의 30~50%를 차단합니다. 사용자가 광고를 보고 사이트에 들어와 구매했는데, 그 구매 이벤트가 광고 플랫폼에 도달하지 못해 ROAS 보고서에 빠지는 일이 매일 일어납니다. Server-side tagging과 각 광고 플랫폼의 Conversion API(CAPI)는 이 데이터 손실을 메우는 표준 답안이 되었습니다. 이 글은 마케터가 서버 측 태깅의 구조와 운영 함정을 이해하고, 자기 회사 측정 인프라를 점검할 수 있도록 코드·아키텍처·운영 관점을 한 글에서 정리합니다.
클라이언트 vs 서버 — 무엇이 다른가
전통적인 마케팅 픽셀(Meta Pixel·GA4·TikTok Pixel)은 사용자의 브라우저에서 직접 광고 플랫폼으로 HTTP 요청을 보냅니다. 이 구조는 단순하지만 다음 영역에서 깨집니다.
클라이언트 측 픽셀이 깨지는 자리
- 광고 차단기 — 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% 떨어집니다. 이메일이 “[email protected]”으로 들어와 그대로 해싱되면 같은 사람이 광고 플랫폼에서 등록한 “[email protected]”과 매칭이 안 됩니다. 아래 코드가 그 정규화 + SHA-256 파이프라인 전체입니다.
import hashlibimport reimport uuidfrom datetime import datetime
# --- PII 정규화 + SHA-256 해싱 ---def normalize_email(email: str) -> str: return email.strip().lower()
def normalize_phone(phone: str) -> str: # E.164 형식으로 변환 후 숫자만 남김 digits = re.sub(r"[^\d]", "", phone) return digits # "821012345678" 형태
def sha256_hash(value: str) -> str: return hashlib.sha256(value.encode("utf-8")).hexdigest()
def build_user_data(email: str, phone: str, client_ip: str, user_agent: str) -> dict: return { "em": [sha256_hash(normalize_email(email))], "ph": [sha256_hash(normalize_phone(phone))], "client_ip_address": client_ip, "client_user_agent": user_agent, }
# 사용 예user_data = build_user_data( phone="+82-10-1234-5678", client_ip="203.0.113.1", user_agent="Mozilla/5.0 ...",)Deduplication — 같은 이벤트가 두 출처로 들어갈 때
CAPI를 도입한다고 클라이언트 픽셀을 끄는 게 아닙니다. 둘 다 보내고 광고 플랫폼이 같은 이벤트를 중복으로 처리하지 않도록 dedup ID(이벤트 ID·event_id)를 함께 보냅니다.
| 출처 | event_id | 결과 |
|---|---|---|
| 클라이언트만 도달 | evt_123 | 1회 카운트 |
| 서버만 도달 | evt_123 | 1회 카운트 |
| 둘 다 도달 | evt_123 (동일) | 1회 카운트 |
| event_id 누락 | — | 2회 중복 카운트 |
event_id가 둘 사이에서 일치해야 dedup이 작동합니다. 이게 어긋나면 측정이 정확해지는 게 아니라 부풀려집니다.
Meta CAPI 실제 호출 — event_id dedup 포함
아래는 Python requests로 Meta CAPI를 직접 호출하는 패턴입니다. event_id를 클라이언트 픽셀과 공유해야 dedup이 작동합니다. 주문이 완료된 시점에 백엔드에서 이 함수를 호출하면 됩니다.
import requestsimport uuidfrom datetime import datetime
META_PIXEL_ID = "YOUR_PIXEL_ID"META_ACCESS_TOKEN = "YOUR_ACCESS_TOKEN"CAPI_URL = f"https://graph.facebook.com/v19.0/{META_PIXEL_ID}/events"
def send_purchase_event( order_id: str, value: float, currency: str, user_data: dict, event_source_url: str,) -> dict: # event_id = 클라이언트 픽셀과 반드시 동일한 값을 사용해야 dedup 작동 event_id = f"order_{order_id}"
payload = { "data": [ { "event_name": "Purchase", "event_time": int(datetime.utcnow().timestamp()), "event_id": event_id, # dedup 핵심 "event_source_url": event_source_url, "action_source": "website", "user_data": user_data, # build_user_data() 결과 "custom_data": { "value": value, "currency": currency, "order_id": order_id, }, } ], "access_token": META_ACCESS_TOKEN, "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": "..."}아키텍처 옵션 — 어디에 서버 컨테이너를 둘 것인가
옵션 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가 좋습니다.
정규화 파이프라인 — PII 해싱 + BigQuery 적재
대량의 오프라인 전환(CRM, 오프라인 구매)을 처리할 때는 Python 파이프라인으로 PII를 해싱하고 BigQuery에 적재해두는 방식이 표준입니다. 아래는 10줄 안에서 핵심 흐름을 표현한 코드입니다.
from google.cloud import bigqueryimport hashlib, re, jsonfrom datetime import datetime
BQ_TABLE = "my_project.capi.hashed_events"
def hash_pii(raw: str, field: str) -> str: if field == "email": raw = raw.strip().lower() elif field == "phone": raw = re.sub(r"[^\d]", "", raw) return hashlib.sha256(raw.encode()).hexdigest()
def insert_event_to_bq(order_id: str, email: str, phone: str, value: float): client = bigquery.Client() rows = [{ "event_id": f"order_{order_id}", "event_time": datetime.utcnow().isoformat(), "hashed_email": hash_pii(email, "email"), "hashed_phone": hash_pii(phone, "phone"), "value": value, "currency": "KRW", }] errors = client.insert_rows_json(BQ_TABLE, rows) if errors: raise RuntimeError(f"BigQuery insert error: {errors}")BigQuery에 쌓인 이벤트는 GA4 BigQuery ROAS 파이프라인과 JOIN해 채널별 전환 기여를 분석하거나, scheduled query로 일별 CAPI 전송 배치를 돌리는 데 사용할 수 있습니다.
운영 함정 — 매주 점검할 만한 것들
중복 이벤트 — 클라이언트 + 서버 양쪽 동시 송출
서버 측 태깅을 도입하면서 클라이언트 픽셀을 끄지 않는 것이 올바른 설정입니다. 하지만 event_id가 제대로 연결되지 않으면 같은 구매가 두 번 카운트됩니다. 플랫폼 보고서에서 갑자기 전환 수가 2배 뛰면 dedup 설정부터 의심합니다.
타임 드리프트 — 서버와 클라이언트의 시간 어긋남
Meta CAPI는 event_time이 실제 이벤트 발생 시각에서 ±1시간 이상 벗어나면 신호를 무시합니다. 서버의 시스템 시각이 잘못 설정된 경우, 또는 배치 처리로 이벤트를 나중에 보낼 때 이 문제가 납니다. event_time은 항상 UTC 기준 실제 발생 시각을 써야 합니다.
네트워크 실패 재시도
외부 CAPI 호출은 일시적인 네트워크 오류·플랫폼 서버 점검으로 실패할 수 있습니다. 재시도 없이 fire-and-forget으로 보내면 구매 이벤트가 통째로 누락됩니다. 최소한 3회 재시도 + exponential backoff 패턴을 권장합니다.
import time
def send_with_retry(payload: dict, max_retries: int = 3) -> dict: for attempt in range(max_retries): try: resp = requests.post(CAPI_URL, json=payload, timeout=5) resp.raise_for_status() return resp.json() except requests.RequestException as e: if attempt == max_retries - 1: raise # 최종 실패 시 예외 전파 (큐·알림 연결) time.sleep(2 ** attempt) # 1s, 2s, 4s측정 신호 끊김 alert 디자인
서버 측 태깅을 도입하고 한 달 내에 마주칠 일은 “어느 날 갑자기 매칭률이 0이 되는 사고”입니다. 다음 alert이 미리 깔려 있어야 합니다.
- 일별 EMQ 점수 ≥ 7 유지 (Meta)
- 일별 서버 이벤트 수 / 일별 매출 비율의 변동성 ≤ 30%
- 서버 이벤트의 평균 매칭 키 개수 ≤ 3 → 알림
이 alert 셋만 있어도 데이터 파이프라인 사고를 며칠 안에 잡을 수 있습니다.
1st-party 데이터 운영의 한 단계 위 — Customer Match·LAL 시드
서버 측 태깅을 운영하면 자연스럽게 1st-party 데이터를 직접 광고 플랫폼에 업로드해 활용할 수 있습니다.
- Customer Match — 자사 고객 이메일 리스트를 업로드해 같은 사람을 광고에서 찾거나 제외
- Lookalike Audience(LAL) — 고가치 고객 시드 리스트로 비슷한 잠재 고객 확장
- Conversion Lift 신호 — 자체 LTV·전환 시그널을 광고 플랫폼 입찰 모델에 직접 입력
세 기능 모두 서버 측 데이터 파이프라인이 정돈되어 있어야 안정적으로 운영됩니다. 고객 식별자가 어떻게 결합되고 관리되는지 더 깊이 들어가려면 CDP ID 그래프 글이 좋은 출발점입니다.
마치며
광고 측정에서 클라이언트 측 픽셀 의존도는 매년 떨어지고 있고, 서버 측 태깅과 CAPI는 더 이상 선택이 아닌 표준입니다. 마케터가 코드를 직접 짤 필요는 없지만, 측정 인프라가 어떤 구조로 돌아가는지·어디서 깨지는지를 알아야 광고 플랫폼 보고서의 변동을 해석할 수 있습니다.
다음 분기에 한 번만 시도해 볼 만한 것은 EMQ 점수와 서버 이벤트 도달률을 분기 KPI에 넣고, 분석팀과 함께 매칭 키 정규화 파이프라인을 한 번 점검하는 흐름입니다. 그 한 번의 점검이 향후 ROAS 보고 정확도를 한 단계 올립니다.
다음 글로 이어가기- CDP ID 그래프 — 1st-party 매칭 키가 어떻게 식별자 그래프로 이어지는지
- GA4 BigQuery ROAS — 서버 이벤트를 BigQuery에서 ROAS로 계산하는 파이프라인
- Customer Segmentation Mixture — 수집한 1st-party 데이터로 세그먼트를 만드는 법
참고
- Meta, “Conversion API documentation”: https://developers.facebook.com/docs/marketing-api/conversions-api/
- Google, “GA4 Measurement Protocol”: https://developers.google.com/analytics/devguides/collection/protocol/ga4
- Google, “Server-side tagging in Tag Manager”: https://developers.google.com/tag-platform/tag-manager/server-side
- TikTok, “Events API”: https://business-api.tiktok.com/portal/docs?id=1771101027431425
- Simo Ahava, “Server-side GTM resources”: https://www.simoahava.com/tags/server-side-tagging/
Analytics Ops (GA4·GTM) 카테고리의 다른 글
전체 보기 →-
2026·05·16
광고 SQL·BI 안티패턴 7가지 — ROAS 보고서를 거짓말로 만드는 SQL 함정
광고 데이터를 SQL로 집계할 때 반복적으로 깨지는 7가지 패턴 — 중복 조인·attribution window 누락·시간대 미스·conversion lag·환율·채널 매핑·dedup. 마케터·BI팀이 실무에서 만나는 함정을 실제 SQL 반례와 함께 정리합니다.
-
2026·05·09
Marketing analytics maturity model — last-click부터 triangulation까지 5단계
마케팅 측정의 성숙도는 5단계로 나뉩니다. last-click → multi-touch → MMM → lift study → triangulation. 우리 팀이 어디 있는지 진단하고 다음 단계 로드맵을 잡는 한 가지 모델.
-
2026·05·06
CDP 시대의 ID 그래프 — 쿠키 없이 유저를 어떻게 잇나
3rd party cookie 종말과 iOS 14.5 이후 유저 식별이 어떻게 바뀌었는지, ID 그래프가 deterministic·probabilistic 매칭으로 어떻게 동작하는지 마케터 시각으로 정리.
-
2026·05·06
GA4 + BigQuery로 ROAS 파이프라인 직접 만들기 — 플랫폼 대시보드 못 믿을 때
Meta 광고 매니저 ROAS와 GA4 ROAS가 30% 차이 납니다. 둘 다 거짓말은 아닌데 어느 쪽도 믿기 어려워요. 원천 이벤트로 직접 ROAS 파이프라인을 만드는 SQL 4단계.