Ir al contenido
HILLS API v1 Acceso anticipado

Guías

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.

Actualizado el

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.

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. 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:

{
  "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 <t>.<raw body> 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á.

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.

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:

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.

Para volver a enviar una entrega, con el mismo id de evento y el mismo payload:

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.

curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/test" \
  -H "Authorization: Bearer $HILLS_API_KEY"
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.

¿Listo para integrar?

Solicita acceso a la API y te respondemos en 1 a 3 días hábiles.