Event API

Stream playback, engagement, and revenue signals to NU. Validated revenue events feed settlements. Requires events:write.

Endpoints

MethodPathSuccessPurpose
POST/events200Send one event
POST/events/batch207Send up to 500 events and return a result per item

Event types

Only the following event_type values are accepted. Unknown types return validation_failed.

event_typeMeaningRequired fieldsSettlement
IMPRESSIONTitle or poster impression—No
TITLE_VIEWTitle detail openedtitle_idNo
EPISODE_STARTEDPlayback startedtitle_id, episode_idNo
PLAYBACK_PROGRESSHeartbeat or quartile progressepisode_id, completion_rateNo
EPISODE_COMPLETEDPlayback completedepisode_id, completion_rateNo
EPISODE_UNLOCKEDPaid unlock grantedepisode_idMetrics only
PAYMENT_COMPLETEDRevenuetitle_id, revenue_amount, currencyYes
REFUNDRefund or reversaltitle_id, revenue_amount, currencyYes (negative)
SUBSCRIPTION_RENEWALRecurring revenuetitle_id, revenue_amount, currencyYes
AD_IMPRESSIONAdvertising or engagement signal—No
OTHERNon-standard—No

Settlement eligibility is derived by the ingestion pipeline from the type, never from a partner-supplied flag.

Required fields and validation

Every event requires event_id and event_type. occurred_at is optional and defaults to server receipt time. Type-specific fields are listed above.

  • event_id is unique per organization and provides idempotency. A duplicate is a no-op returning 409 duplicate_event_id; it is not counted twice.
  • completion_rate must be within [0, 1].
  • Revenue types require title_id, revenue_amount, and ISO 4217 currency; otherwise they return validation_failed. Revenue must be attributed to a title so it can be reconciled.
  • occurred_at cannot be in the future. Future-skewed events are rejected as failed with occurred_at_in_future. Events older than 24 hours are accepted but marked payload.late = true.
  • viewer_id_hash must be a hashed identifier and contain no raw PII; otherwise the event returns validation_failed.
  • When sent, title_id, episode_id, and playback_session_id must be visible in the key’s environment and belong to your organization. Reference failures return validation_failed with details such as unknown_title_for_environment, unknown_episode_for_environment, playback_session_mismatch, license_not_active, or episode_not_licensed.

Field limits

String fields have length limits; values over the limit return validation_failed:

FieldTypeConstraint
event_idstring1–200 characters (required)
event_typestring1–64 characters (required)
title_idstringAt most 64 characters
episode_idstringAt most 64 characters
playback_session_idstringAt most 64 characters
viewer_id_hashstringAt most 128 characters; hash only
countrystringAt most 8 characters
currencystringAt most 8 characters
devicestringAt most 64 characters
completion_ratenumber0–1
payloadobjectFree-form JSON, at most 4 KB serialized

payload is an optional free-form JSON object for partner metadata. Its serialized size must be at most 4,096 bytes (4 KB), or it returns validation_failed with The value did not pass validation..

Send one event

curl -X POST "https://signal-partners.newunivers.ai/v1/events" \
  -H "Authorization: Bearer nsp_live_xxx" \
  -H "X-NU-Partner-Id: org_acme" \
  -H "Accept-Language: en" \
  -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": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
    "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" } }

Successful single-event ingestion returns HTTP 200, status: "validated", and an ISO-8601 received_at timestamp.

Duplicate

Sending the same event_id again returns:

{
  "error": {
    "code": "duplicate_event_id",
    "message": "The event ID was already accepted.",
    "request_id": "req_..."
  }
}

Validation error

{
  "error": {
    "code": "validation_failed",
    "message": "Request validation failed.",
    "request_id": "req_...",
    "details": [
      {
        "field": "currency",
        "issue": "required",
        "message": "A required value is missing."
      }
    ]
  }
}

Batch events

Send up to 500 events per request. Each item is validated independently; the response is HTTP 207 with per-item results, so the request can succeed even when some items fail.

curl -X POST "https://signal-partners.newunivers.ai/v1/events/batch" \
  -H "Authorization: Bearer nsp_live_xxx" \
  -H "X-NU-Partner-Id: org_acme" \
  -H "Accept-Language: en" \
  -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 is the submitted count (1–500). Each item’s status is exactly one of accepted | duplicated | failed. Items do not include an error_code field.

Idempotency

Server-side deduplication by event_id makes retries safe. Reuse the same event_id for retries and generate it for the real event, not each HTTP attempt. Check ingestion status in the response and your request logs. Accepted events are stored as VALIDATED; repeated IDs become duplicated no-ops. Revenue events feed settlements.