# Webhooks > Recibe eventos de HILLS Music en tu endpoint HTTPS: suscripción, los 13 tipos de evento, el sobre, la firma X-Hills-Signature, reintentos y registro. Los webhooks avisan a tu sistema cuando ocurre algo en HILLS Music, para que no tengas que sondear. HILLS envía un `POST` firmado a tu endpoint HTTPS por cada evento al que te suscribas. Los webhooks se incluyen con la API en los planes Enterprise y Custom. ## Suscríbete Crea un endpoint con `POST /webhooks` (ámbito `webhooks:write`). También puedes añadirlo en la aplicación web de HILLS, en **Settings → Webhooks**. ```bash curl -X POST "https://api.hillsmusic.com/v1/webhooks" \ -H "Authorization: Bearer $HILLS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/hills/webhooks","description":"Order system","events":["release.approved","delivery.store_status_changed"]}' ``` La respuesta (`201`) contiene el endpoint y su secreto de firma, `whsec_…`. **El secreto se muestra una sola vez.** Guárdalo con tus demás secretos. La URL debe ser `https://` en el puerto 443 y con una dirección pública. Las direcciones privadas, de loopback, de enlace local y de metadatos de la nube se rechazan con `400 invalid_url`, tanto al guardar la URL como en cada solicitud. Puedes tener hasta 20 endpoints (a partir de ahí, `409 too_many`). Todas las solicitudes de endpoints de webhook, con sus campos y errores, están en la [referencia de Webhooks](https://developer.hillsmusic.com/es/reference/webhooks/). `GET /webhooks/events` enumera los tipos de evento con el significado de cada uno. ## Eventos | Evento | Se envía cuando | | --- | --- | | `release.submitted` | Se envió un lanzamiento a revisión. | | `release.approved` | Se aprobó un lanzamiento y está en cola para sus plataformas. | | `release.changes_requested` | Se devolvió un lanzamiento con lo que hay que corregir. | | `release.live` | Un lanzamiento se publicó en su primera plataforma. | | `delivery.store_status_changed` | Cambió el estado de un lanzamiento en una plataforma: en camino, enviado, publicado, necesita tu corrección, retirada solicitada o retirado. | | `takedown.completed` | Todas las plataformas de una solicitud de retirada aprobada han retirado el lanzamiento. | | `ddex.import.report_ready` | Terminó una prueba DDEX (la carpeta `/test`, la aplicación web o `POST /ddex/validate`); su informe está listo. | | `ddex.import.imported` | Terminó la importación de un lote DDEX entregado a producción: done, partial o failed. | | `statement.published` | La liquidación de ingresos de un mes está lista (en marca blanca, primero lista para que la revises). | | `payout.status_changed` | Una solicitud de pago se aprobó, se envió, se pagó, falló o se canceló. | | `request.status_changed` | HILLS Music respondió a una solicitud: aprobada, necesita cambios, completada o rechazada. | | `chat.message` | HILLS Music te escribió en el soporte en vivo. | | `webhook.test` | Solo lo envía `POST /webhooks/{id}/test`. No puedes suscribirte a él. | ## El sobre Todos los eventos tienen la misma forma exterior: ```json { "id": "5d0c2b7e-8a43-4c4e-9f1e-2f0b9a6c1d33", "type": "release.approved", "api_version": "v1", "occurred_at": "2026-10-13T09:05:00Z", "account_id": 12, "data": { "release": { "id": 812, "title": "Midnight Run" } } } ``` | Campo | Significado | | --- | --- | | `id` | El UUID del evento. Es el mismo en todos los intentos: úsalo para descartar duplicados. | | `type` | El tipo de evento. | | `api_version` | `v1`. Dentro de una versión, los campos solo se añaden. | | `occurred_at` | Cuándo ocurrió, en ISO 8601 y en UTC. | | `account_id` | Tu cuenta de socio. | | `data` | Los datos del evento. Su forma depende de `type`. | Cada solicitud lleva además `X-Hills-Signature`, `X-Hills-Event` (el tipo), `X-Hills-Delivery` (el id de la entrega, el mismo en todos los intentos), `X-Hills-Attempt` (1, 2, 3…), `Content-Type: application/json` y `User-Agent: HILLS-Webhooks/1.0`. Un cuerpo ocupa como máximo 256 KB. Un evento más grande se envía con `data.truncated: true` y solo sus ids. Lee el resto en la API. No se garantiza que los eventos lleguen en orden. Usa `occurred_at` y, cuando el orden importe, lee el estado actual en la API. ## Verifica la firma `X-Hills-Signature` tiene este aspecto: `t=1792141500,v1=5257a869…`. Para comprobarla: 1. Lee `t` (segundos Unix) y `v1` de la cabecera. 2. Rechaza la solicitud si `t` se aleja más de 5 minutos de tu reloj. Así se evitan los ataques de repetición. 3. Calcula el HMAC-SHA256 de `.` con tu secreto tal cual como clave (incluido `whsec_`). Codifícalo en hexadecimal. 4. Compáralo con `v1` en tiempo constante. Usa los bytes del cuerpo **sin procesar**. Un JSON analizado y vuelto a serializar no coincidirá. ```js import crypto from "node:crypto"; import express from "express"; const SECRET = process.env.HILLS_WEBHOOK_SECRET; // whsec_… const app = express(); app.post("/hills/webhooks", express.raw({ type: "application/json" }), (req, res) => { const header = req.get("X-Hills-Signature") ?? ""; const parts = Object.fromEntries(header.split(",").map((kv) => kv.split("=", 2))); const t = Number(parts.t); if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(400); const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${req.body}`).digest("hex"); const a = Buffer.from(expected, "hex"); const b = Buffer.from(parts.v1 ?? "", "hex"); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) return res.sendStatus(400); const event = JSON.parse(req.body.toString("utf8")); // Dedupe on event.id: a retry after a lost answer sends the same event again. queue.push(event); // do the work later res.sendStatus(200); // answer within 10 seconds }); app.listen(3000); ``` ## Responde rápido Cualquier `2xx` en menos de 10 segundos es un éxito. Todo lo demás es un fallo, incluidos un tiempo de espera agotado o un `3xx` (no se siguen las redirecciones). Responde primero y haz después el trabajo en una cola. ## Reintentos Una entrega fallida se vuelve a intentar, hasta 8 intentos en total. Tras cada fallo, el siguiente intento llega **1 minuto, 5 minutos, 30 minutos, 2 horas, 12 horas, 12 horas y 12 horas** después: en total, aproximadamente día y medio. Tras el 8.º fallo la entrega se abandona, pero todavía puedes [reintentarla a mano](#the-delivery-log). ## Desactivación automática Un endpoint que falla 20 intentos seguidos, sin ningún éxito entre medias, se desactiva. Recibes una notificación. Las entregas pendientes en ese momento se abandonan. `GET /webhooks/{id}` muestra el motivo: `enabled: false`, `disabled_at` y `disabled_reason`. Corrige tu endpoint y vuelve a activarlo: ```bash curl -X PATCH "https://api.hillsmusic.com/v1/webhooks/7" \ -H "Authorization: Bearer $HILLS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"enabled":true}' ``` Eso pone a cero `failure_count`. Después, reintenta lo que se abandonó. ## El registro de entregas `GET /webhooks/{id}/deliveries` enumera lo que se envió en los últimos 30 días, de lo más reciente a lo más antiguo: el payload, los intentos, el último estado HTTP, la hora y el error. Filtra con `status` (`pending`, `succeeded`, `failed`) y `event`. Se pagina con `before`. Consulta [Paginación](https://developer.hillsmusic.com/es/guides/pagination/). Para volver a enviar una entrega, con el mismo id de evento y el mismo payload: ```bash curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/deliveries/1042/retry" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` Mientras el endpoint esté desactivado, el reintento y la prueba responden `409 endpoint_disabled`. ## Renueva el secreto `POST /webhooks/{id}/roll-secret` genera un secreto nuevo. La respuesta lo incluye, una sola vez. **El secreto anterior deja de funcionar al instante**, así que planifica el cambio: 1. Haz que tu verificador acepte una lista de secretos y pruebe cada uno por turno. 2. Renueva el secreto y añade el nuevo a la lista. 3. Cuando las entregas se validen con el secreto nuevo, quita el anterior. Renuévalo de inmediato si el secreto se filtra. ## Envía un evento de prueba `POST /webhooks/{id}/test` pone en cola un evento `webhook.test` para el endpoint, esté suscrito a los eventos que esté. Se firma como cualquier otro evento y su resultado aparece en el registro de entregas. ```bash curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/test" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` ```js const res = await fetch("https://api.hillsmusic.com/v1/webhooks/7/test", { method: "POST", headers: { Authorization: `Bearer ${process.env.HILLS_API_KEY}` }, }); const { delivery } = await res.json(); // 202; check GET /webhooks/7/deliveries for the outcome ``` Para hacer pruebas en tu propio equipo, usa un túnel que te dé una URL HTTPS pública. Fuente: https://developer.hillsmusic.com/es/guides/webhooks/