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étodo | Ruta | Éxito | Finalidad |
|---|---|---|---|
| POST | /events | 200 | Enviar un evento |
| POST | /events/batch | 207 | Enviar hasta 500 eventos con resultado por elemento |
Tipos de evento
Solo se aceptan estos valores de event_type. Los desconocidos devuelven validation_failed.
| event_type | Significado | Campos obligatorios | Liquidación |
|---|---|---|---|
IMPRESSION | Impresión de título o póster | — | No |
TITLE_VIEW | Apertura del detalle | title_id | No |
EPISODE_STARTED | Inicio de reproducción | title_id, episode_id | No |
PLAYBACK_PROGRESS | Heartbeat o progreso por cuartil | episode_id, completion_rate | No |
EPISODE_COMPLETED | Reproducción completada | episode_id, completion_rate | No |
EPISODE_UNLOCKED | Desbloqueo de pago concedido | episode_id | Solo métricas |
PAYMENT_COMPLETED | Ingresos | title_id, revenue_amount, currency | Sí |
REFUND | Reembolso o reversión | title_id, revenue_amount, currency | Sí (negativo) |
SUBSCRIPTION_RENEWAL | Ingreso recurrente | title_id, revenue_amount, currency | Sí |
AD_IMPRESSION | Señal publicitaria o de interacción | — | No |
OTHER | No 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_ides único por organización e idempotente. Un duplicado no hace nada, devuelve409 duplicate_event_idy no se cuenta dos veces.completion_ratedebe estar en[0, 1].- Los tipos de ingreso exigen
title_id,revenue_amountycurrencyISO 4217; si no, devuelvenvalidation_failed. El ingreso debe atribuirse a un título. occurred_atno puede estar en el futuro. Se rechaza comofailedconoccurred_at_in_future. Eventos de más de 24 horas se aceptan pero se marcanpayload.late = true.viewer_id_hashdebe ser un identificador con hash sin PII original; si no, devuelvevalidation_failed.- Si se envían,
title_id,episode_idyplayback_session_iddeben ser visibles en el entorno y pertenecer a su organización. Los fallos devuelvenvalidation_failedconunknown_title_for_environment,unknown_episode_for_environment,playback_session_mismatch,license_not_activeoepisode_not_licensed.
Límites de campos
Los campos de texto tienen límites; superarlos devuelve validation_failed:
| Campo | Tipo | Restricción |
|---|---|---|
event_id | string | 1–200 caracteres (obligatorio) |
event_type | string | 1–64 caracteres (obligatorio) |
title_id | string | Máximo 64 caracteres |
episode_id | string | Máximo 64 caracteres |
playback_session_id | string | Máximo 64 caracteres |
viewer_id_hash | string | Máximo 128 caracteres; solo hash |
country | string | Máximo 8 caracteres |
currency | string | Máximo 8 caracteres |
device | string | Máximo 64 caracteres |
completion_rate | number | 0–1 |
payload | object | JSON 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.