분석 이벤트 API 맥락

스펙 원본은 frontmatter spec:. 필드 계약은 as-built §10 analytics schema v2. 이 문서는 v2로 바꾼 이유, enum 의미, 중복 제거 규칙만.

한눈에

엔드포인트역할 한 줄
POST /v1/analytics/events방문·행동 이벤트 수집. 항상 202 + 빈 body (fire-and-forget)

왜 v2인가

  • v1은 공개 요청이 내부 businessCardId를 들고 다녔다. v2는 공개 식별자(cardSlug)만 받고 서버가 내부 ID를 역해석 — 내부 ID가 브라우저에 노출되지 않는다.
  • 재시도·중복 문제를 클라이언트 clientEventId + DB unique index(event_key)로 해소. 이미 처리된 ID도 202 — 클라이언트는 성공·중복을 구분할 필요 없다.
  • SHARE sourceType 신설 이유: 공유받은 방문을 SOCIAL/LINK로 뭉개면 NFC 유입과 공유 유입을 분리한다는 기능 목표 자체가 무의미해짐.

이벤트 · 소스 의미

  • eventType: PAGE_VIEW · NFC_TAP · LINK_CLICK · CARD_DOWNLOAD · SHARE_CLICK
  • sourceType: DIRECT(기본) · NFC · QR · LINK · SEARCH · SOCIAL · SHARE · UNKNOWN
  • SHARE_CLICK공유 버튼을 누른 시점의 이벤트 — 공유 시트에서 사용자가 취소했는지는 플랫폼별로 알 수 없어 정의에서 제외.
  • ?source=nfc로 들어온 방문자가 공유하면 그 공유 URL은 source=share — 공유받은 방문이 NFC로 집계되지 않는다.
  • NFC_TAP은 서버 전용 기록(키 nfc:{visitId}) — 상세는 nfc-card.md.

불변조건

  • 공개 요청에서 businessCardId, nfcCardId, 원문 referrer, city를 받지 않는다.
  • cardSlug현재 공개 명함이어야 함 — 아니면 404. cardLinkId는 그 명함 소유 링크인지까지 검증.
  • 전송 실패가 화면 동작을 막지 않는다(fire-and-forget).

엣지케이스 · gotcha

  • crypto.randomUUID 없는 구형 브라우저의 FE fallback 값은 BE의 UUID/ULID 검증에 걸릴 수 있다(알려진 제한) — 특정 브라우저군 이벤트 유실이 보이면 이것부터.
  • 서로 다른 인스턴스가 같은 event key를 완전 동시 저장하면 한쪽은 unique constraint 오류(알려진 경쟁, 저장 전 존재 확인 + index가 최종 방어).
  • visitor ID는 localStorage, session ID는 sessionStorage — 탭·기기 단위 집계 차이는 여기서 나온다.

관련 문서