이벤트 API

재생, 인게이지먼트, 매출 신호를 NU로 스트리밍합니다. 검증된 매출 이벤트는 정산에 반영됩니다. events:write 스코프가 필요합니다.

엔드포인트

MethodPathSuccess용도
POST/events200단일 이벤트 전송
POST/events/batch207최대 500개 이벤트 전송, 항목별 결과 반환

이벤트 타입

다음 event_type 값만 허용됩니다. 알 수 없는 타입은 validation_failed를 반환합니다.

event_type의미필수 필드정산
IMPRESSION타이틀/포스터 노출아니오
TITLE_VIEW타이틀 상세 열림title_id아니오
EPISODE_STARTED재생 시작title_id, episode_id아니오
PLAYBACK_PROGRESS하트비트/사분위 진행episode_id, completion_rate아니오
EPISODE_COMPLETED재생 완료episode_id, completion_rate아니오
EPISODE_UNLOCKED유료 잠금 해제 부여episode_id지표 전용
PAYMENT_COMPLETED매출title_id, revenue_amount, currency
REFUND환불(취소)title_id, revenue_amount, currency예 (음수)
SUBSCRIPTION_RENEWAL반복 매출title_id, revenue_amount, currency
AD_IMPRESSION광고/인게이지먼트 신호아니오
OTHER비표준아니오

정산 대상 여부는 인제스천 파이프라인이 타입으로부터 도출하며, 파트너가 제공한 플래그로는 절대 결정되지 않습니다.

필수 필드 & 검증 규칙

모든 이벤트에는 event_id event_type가 필요합니다. occurred_at은 선택이며 (기본값은 서버 수신 시각). 타입별 필수 필드는 위에 정리되어 있습니다.

  • event_id조직별로 고유하며 멱등성에 사용됩니다 — 중복은 무연산(no-op)으로 409 duplicate_event_id를 반환합니다(중복 집계 없음).
  • completion_rate[0, 1] 범위여야 합니다.
  • 매출 타입은 title_id, revenue_amount, ISO 4217 currency가 필요하며, 그렇지 않으면 validation_failed입니다 — 매출은 반드시 타이틀에 귀속되어야 하며 그렇지 않으면 대사(reconcile)할 수 없습니다.
  • occurred_at미래 시각이면 안 됩니다 — 미래로 치우친 이벤트는 failed로 거부됩니다(occurred_at_in_future). 24시간보다 오래된 이벤트는 여전히 허용되지만 payload.late = true로 표시됩니다.
  • viewer_id_hash는 해시된 식별자여야 하며 원본 PII를 절대 포함해서는 안 됩니다(그렇지 않으면 validation_failed).
  • title_id, episode_id, playback_session_id를 전송할 때, 이들은 키의 환경에서 조회 가능해야 하며 귀하의 조직에 속해야 합니다. 참조 실패 시 unknown_title_for_environment, unknown_episode_for_environment, playback_session_mismatch, license_not_active, episode_not_licensed와 같은 상세 정보와 함께 validation_failed를 반환합니다.

필드 제한

문자열 필드는 길이가 제한되며, 초과 시 validation_failed로 거부됩니다:

필드타입제약
event_idstring1–200자 (필수)
event_typestring1–64자 (필수)
title_idstring≤ 64자
episode_idstring≤ 64자
playback_session_idstring≤ 64자
viewer_id_hashstring≤ 128자, 해시만 허용
countrystring≤ 8자
currencystring≤ 8자
devicestring≤ 64자
completion_ratenumber0–1
payloadobject자유 형식 JSON, 직렬화 시 ≤ 4KB

payload는 파트너별 메타데이터를 위한 선택적 자유 형식 JSON 객체입니다. 직렬화된 크기는 ≤ 4096바이트 (4KB)여야 하며, 그렇지 않으면validation_failed입니다(payload exceeds 4KB).

단일 이벤트 전송

curl -X POST "https://nu-signal-partners.vercel.app/v1/events" \
  -H "Authorization: Bearer nsp_live_xxx" \
  -H "X-NU-Partner-Id: org_acme" \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "evt_acme_0001",
    "event_type": "PAYMENT_COMPLETED",
    "title_id": "ttl_abc",
    "episode_id": "ep_001",
    "viewer_id_hash": "9f86d081884c7d65...",
    "country": "KR",
    "device": "web",
    "revenue_amount": 3.99,
    "currency": "USD",
    "occurred_at": "2026-06-24T00:00:00Z"
  }'
{ "data": { "event_id": "evt_acme_0001", "status": "validated", "received_at": "2026-06-24T00:00:05.123Z" } }

단일 이벤트 인제스천이 성공하면 HTTP 200과 함께 status: "validated" 및 ISO-8601 received_at 타임스탬프를 반환합니다.

중복

동일한 event_id를 다시 전송하면 다음을 반환합니다:

{ "error": { "code": "duplicate_event_id", "message": "event_id already accepted", "request_id": "req_..." } }

검증 오류

{
  "error": {
    "code": "validation_failed",
    "message": "event validation failed",
    "request_id": "req_...",
    "details": [{ "field": "currency", "issue": "required" }]
  }
}

배치 이벤트

요청당 최대 500개의 이벤트를 전송할 수 있습니다. 각 항목은 독립적으로 검증되며 응답은 항목별 결과를 담은 HTTP 207입니다(일부 항목이 실패해도 요청 자체는 성공합니다).

curl -X POST "https://nu-signal-partners.vercel.app/v1/events/batch" \
  -H "Authorization: Bearer nsp_live_xxx" \
  -H "X-NU-Partner-Id: org_acme" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      { "event_id": "evt_1", "event_type": "EPISODE_STARTED", "title_id": "ttl_abc", "episode_id": "ep_001", "occurred_at": "2026-06-24T00:00:00Z" },
      { "event_id": "evt_1", "event_type": "EPISODE_STARTED", "title_id": "ttl_abc", "episode_id": "ep_001", "occurred_at": "2026-06-24T00:00:01Z" },
      { "event_id": "evt_3", "event_type": "PAYMENT_COMPLETED", "occurred_at": "2026-06-24T00:00:02Z" }
    ]
  }'
{
  "data": {
    "received": 3,
    "accepted": 1,
    "duplicated": 1,
    "failed": 1,
    "results": [
      { "event_id": "evt_1", "status": "accepted" },
      { "event_id": "evt_1", "status": "duplicated" },
      { "event_id": "evt_3", "status": "failed" }
    ]
  }
}

received는 제출된 이벤트 수(1–500)입니다. 각 항목의 status는 정확히 accepted | duplicated | failed 중 하나입니다. 항목에는 error_code 필드가 포함되지 않습니다.

멱등성

event_id가 서버 측에서 중복을 제거하므로 안전한 재시도가 쉽습니다. 재시도 시 동일한 event_id를 재사용하세요. (HTTP 시도마다가 아니라) 기반이 되는 실제 이벤트에 대해 안정적인 id를 생성하세요. 인제스천 상태는 HTTP 응답과 자체 요청 로그에서 확인하세요. 허용된 이벤트는 VALIDATED로 저장되며, 반복된 event_id duplicated(무연산)입니다. 매출 이벤트는 정산에 반영됩니다.