API de eventos

Envíe señales de reproducción, interacción e ingresos a NU. Los eventos de ingresos validados alimentan liquidaciones. Requiere events:write.

Endpoints

MétodoRutaÉxitoFinalidad
POST/events200Enviar un evento
POST/events/batch207Enviar hasta 500 eventos con resultado por elemento

Tipos de evento

Solo se aceptan estos valores de event_type. Los desconocidos devuelven validation_failed.

event_typeSignificadoCampos obligatoriosLiquidación
IMPRESSIONImpresión de título o póster—No
TITLE_VIEWApertura del detalletitle_idNo
EPISODE_STARTEDInicio de reproduccióntitle_id, episode_idNo
PLAYBACK_PROGRESSHeartbeat o progreso por cuartilepisode_id, completion_rateNo
EPISODE_COMPLETEDReproducción completadaepisode_id, completion_rateNo
EPISODE_UNLOCKEDDesbloqueo de pago concedidoepisode_idSolo métricas
PAYMENT_COMPLETEDIngresostitle_id, revenue_amount, currencySí
REFUNDReembolso o reversióntitle_id, revenue_amount, currencySí (negativo)
SUBSCRIPTION_RENEWALIngreso recurrentetitle_id, revenue_amount, currencySí
AD_IMPRESSIONSeñal publicitaria o de interacción—No
OTHERNo estándar—No

La elegibilidad se deriva del tipo en la ingesta, nunca de una marca enviada por el socio.

Campos obligatorios y validación

Todo evento exige event_id y event_type. occurred_at es opcional y usa la hora de recepción. Los campos por tipo figuran arriba.

  • event_id es único por organización e idempotente. Un duplicado no hace nada, devuelve 409 duplicate_event_id y no se cuenta dos veces.
  • completion_rate debe estar en [0, 1].
  • Los tipos de ingreso exigen title_id, revenue_amount y currency ISO 4217; si no, devuelven validation_failed. El ingreso debe atribuirse a un título.
  • occurred_at no puede estar en el futuro. Se rechaza como failed con occurred_at_in_future. Eventos de más de 24 horas se aceptan pero se marcan payload.late = true.
  • viewer_id_hash debe ser un identificador con hash sin PII original; si no, devuelve validation_failed.
  • Si se envían, title_id, episode_id y playback_session_id deben ser visibles en el entorno y pertenecer a su organización. Los fallos devuelven validation_failed con unknown_title_for_environment, unknown_episode_for_environment, playback_session_mismatch, license_not_active o episode_not_licensed.

Límites de campos

Los campos de texto tienen límites; superarlos devuelve validation_failed:

CampoTipoRestricción
event_idstring1–200 caracteres (obligatorio)
event_typestring1–64 caracteres (obligatorio)
title_idstringMáximo 64 caracteres
episode_idstringMáximo 64 caracteres
playback_session_idstringMáximo 64 caracteres
viewer_id_hashstringMáximo 128 caracteres; solo hash
countrystringMáximo 8 caracteres
currencystringMáximo 8 caracteres
devicestringMáximo 64 caracteres
completion_ratenumber0–1
payloadobjectJSON libre, máximo 4 KB serializado

payload es JSON libre opcional para metadatos. Su tamaño serializado debe ser como máximo 4.096 bytes; si no, devuelve validation_failed con El valor no es válido..

Enviar un evento

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: es" \
  -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" } }

La ingesta correcta devuelve HTTP 200, status: "validated" y una marca ISO-8601 received_at.

Duplicado

Volver a enviar el mismo event_id devuelve:

{
  "error": {
    "code": "duplicate_event_id",
    "message": "El ID de evento ya fue aceptado.",
    "request_id": "req_..."
  }
}

Error de validación

{
  "error": {
    "code": "validation_failed",
    "message": "La validación de la solicitud ha fallado.",
    "request_id": "req_...",
    "details": [
      {
        "field": "currency",
        "issue": "required",
        "message": "Falta un valor obligatorio."
      }
    ]
  }
}

Eventos por lotes

Envíe hasta 500 eventos por solicitud. Cada elemento se valida aparte; devuelve HTTP 207 con resultados por elemento, aunque algunos fallen.

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: es" \
  -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 es la cantidad enviada (1–500). El status de cada elemento es uno de accepted | duplicated | failed. No incluyen error_code.

Idempotencia

La deduplicación por event_id hace seguros los reintentos. Reutilice el mismo event_id y genérelo por evento real, no por intento HTTP. Revise respuesta y registros. Los aceptados se guardan como VALIDATED; repetidos son duplicated sin efecto. Los ingresos alimentan liquidaciones.