huny.log

기술 포스트 · 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_namePurchase·Lead·AddToCart 등
event_timeUNIX timestamp
user_data해싱된 이메일·전화·이름·IP·user agent
custom_data매출·통화·콘텐츠 ID
event_source_url이벤트가 발생한 페이지
action_sourcewebsite·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 hashlib
import re
import uuid
from 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(
email="[email protected]", # 그대로 넣어도 normalize가 처리
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_1231회 카운트
서버만 도달evt_1231회 카운트
둘 다 도달evt_123 (동일)1회 카운트
event_id 누락2회 중복 카운트

event_id가 둘 사이에서 일치해야 dedup이 작동합니다. 이게 어긋나면 측정이 정확해지는 게 아니라 부풀려집니다.

Meta CAPI 실제 호출 — event_id dedup 포함

아래는 Python requests로 Meta CAPI를 직접 호출하는 패턴입니다. event_id를 클라이언트 픽셀과 공유해야 dedup이 작동합니다. 주문이 완료된 시점에 백엔드에서 이 함수를 호출하면 됩니다.

import requests
import uuid
from 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 bigquery
import hashlib, re, json
from 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 보고 정확도를 한 단계 올립니다.

다음 글로 이어가기

참고

Analytics Ops (GA4·GTM) 카테고리의 다른 글

전체 보기 →