이벤트 API
재생, 인게이지먼트, 매출 신호를 NU로 스트리밍합니다. 검증된 매출 이벤트는 정산에 반영됩니다. events:write 스코프가 필요합니다.
엔드포인트
| Method | Path | Success | 용도 |
|---|---|---|---|
| POST | /events | 200 | 단일 이벤트 전송 |
| POST | /events/batch | 207 | 최대 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 4217currency가 필요하며, 그렇지 않으면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_id | string | 1–200자 (필수) |
event_type | string | 1–64자 (필수) |
title_id | string | ≤ 64자 |
episode_id | string | ≤ 64자 |
playback_session_id | string | ≤ 64자 |
viewer_id_hash | string | ≤ 128자, 해시만 허용 |
country | string | ≤ 8자 |
currency | string | ≤ 8자 |
device | string | ≤ 64자 |
completion_rate | number | 0–1 |
payload | object | 자유 형식 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(무연산)입니다. 매출 이벤트는 정산에 반영됩니다.