Pular para o conteúdo
HILLS API v1 Acesso antecipado

Guias

Webhooks

Receba eventos da HILLS Music no seu endpoint HTTPS: assinatura, os 13 tipos de evento, o envelope, o X-Hills-Signature, novas tentativas e o log.

Atualizado em

Os webhooks avisam o seu sistema quando algo acontece na HILLS Music, para que você não precise fazer polling. A HILLS envia um POST assinado para o seu endpoint HTTPS a cada evento que você assina. Os webhooks vêm com a API nos planos Enterprise e Custom.

Assine eventos

Crie um endpoint com POST /webhooks (escopo webhooks:write). Você também pode adicionar um no app web da HILLS, em 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"]}'

A resposta (201) traz o endpoint e o seu segredo de assinatura, whsec_…. O segredo aparece uma única vez. Guarde-o junto com os seus outros segredos.

A URL precisa ser https:// na porta 443, em um endereço público. Endereços privados, de loopback, link-local e de metadados de nuvem são recusados com 400 invalid_url, tanto quando você salva a URL quanto em cada requisição. Você pode ter até 20 endpoints (409 too_many a partir daí).

Todas as requisições de endpoints de webhook, com seus campos e erros, estão na referência de Webhooks. GET /webhooks/events lista os tipos de evento com o significado de cada um.

Eventos

Evento Enviado quando
release.submitted Um lançamento foi enviado para revisão.
release.approved Um lançamento foi aprovado e está na fila para as suas plataformas.
release.changes_requested Um lançamento foi devolvido com o que precisa ser corrigido.
release.live Um lançamento entrou no ar na primeira plataforma.
delivery.store_status_changed O status de um lançamento em uma plataforma mudou: a caminho, enviado, no ar, requer correção sua, remoção solicitada ou removido.
takedown.completed Todas as plataformas de uma solicitação de remoção aprovada removeram o lançamento.
ddex.import.report_ready Um teste DDEX terminou (a pasta /test, o app web ou POST /ddex/validate); o relatório está pronto.
ddex.import.imported Um lote DDEX entregue em produção terminou de ser importado: done, partial ou failed.
statement.published O relatório de royalties de um mês está pronto (no white label, primeiro pronto para a sua revisão).
payout.status_changed Uma solicitação de pagamento foi aprovada, enviada, paga, falhou ou foi cancelada.
request.status_changed A HILLS Music respondeu a uma solicitação: aprovada, requer alterações, concluída ou recusada.
chat.message A HILLS Music escreveu para você no Live support.
webhook.test Enviado apenas por POST /webhooks/{id}/test. Não é possível assiná-lo.

O envelope

Todo evento tem o mesmo formato externo:

{
  "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 O UUID do evento. É o mesmo em todas as tentativas: use-o para eliminar duplicatas.
type O tipo do evento.
api_version v1. Dentro de uma versão, campos só são adicionados.
occurred_at Quando aconteceu, em ISO 8601 e UTC.
account_id A sua conta de cliente.
data Os dados do evento. O formato depende de type.

Cada requisição também traz X-Hills-Signature, X-Hills-Event (o tipo), X-Hills-Delivery (o id da entrega, o mesmo em todas as tentativas), X-Hills-Attempt (1, 2, 3…), Content-Type: application/json e User-Agent: HILLS-Webhooks/1.0.

Um corpo tem no máximo 256 KB. Um evento maior é enviado com data.truncated: true e apenas os ids. Leia o restante pela API.

Não há garantia de que os eventos cheguem em ordem. Use occurred_at e leia o estado atual pela API quando a ordem importar.

Verifique a assinatura

X-Hills-Signature tem o formato t=1792141500,v1=5257a869…. Para verificá-la:

  1. Leia t (segundos Unix) e v1 no header.
  2. Recuse a requisição se t estiver a mais de 5 minutos do seu relógio. Isso impede ataques de replay.
  3. Calcule o HMAC-SHA256 de <t>.<raw body>, usando o seu segredo exatamente como está (incluindo whsec_) como chave. Codifique o resultado em hexadecimal.
  4. Compare-o com v1 em tempo constante.

Use os bytes brutos do corpo. Um JSON interpretado e serializado de novo não vai bater.

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);

Responda rápido

Qualquer 2xx em até 10 segundos é sucesso. Qualquer outra coisa é falha, inclusive um timeout ou um 3xx (redirecionamentos não são seguidos). Responda primeiro e faça o trabalho depois, em uma fila.

Novas tentativas

Uma entrega que falha é tentada de novo, até 8 tentativas no total. Depois de cada falha, a próxima tentativa vem 1 minuto, 5 minutos, 30 minutos, 2 horas, 12 horas, 12 horas e 12 horas depois: cerca de um dia e meio no total. Depois da 8ª falha, a entrega é abandonada, mas você ainda pode reenviá-la manualmente.

Desativação automática

Um endpoint que falha 20 tentativas seguidas, sem nenhum sucesso no meio, é desativado. Você recebe uma notificação. As entregas pendentes naquele momento são abandonadas.

GET /webhooks/{id} mostra o motivo: enabled: false, disabled_at e disabled_reason. Corrija o seu endpoint e reative-o:

curl -X PATCH "https://api.hillsmusic.com/v1/webhooks/7" \
  -H "Authorization: Bearer $HILLS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"enabled":true}'

Isso zera failure_count. Depois, reenvie o que foi abandonado.

O log de entregas

GET /webhooks/{id}/deliveries lista o que foi enviado nos últimos 30 dias, das mais recentes para as mais antigas: o payload, as tentativas, o último status HTTP, o horário e o erro. Filtre com status (pending, succeeded, failed) e event. A paginação é feita com before. Veja Paginação.

Para enviar uma entrega de novo, com o mesmo id de evento e o mesmo payload:

curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/deliveries/1042/retry" \
  -H "Authorization: Bearer $HILLS_API_KEY"

O reenvio e o teste respondem 409 endpoint_disabled enquanto o endpoint estiver desativado.

Renove o segredo

POST /webhooks/{id}/roll-secret gera um novo segredo. A resposta o traz, uma única vez. O segredo antigo para de funcionar na hora, então planeje a troca:

  1. Faça o seu verificador aceitar uma lista de segredos: teste um de cada vez.
  2. Renove o segredo e adicione o novo à lista.
  3. Quando as entregas passarem com o novo segredo, remova o antigo.

Renove o segredo imediatamente se ele vazar.

Envie um evento de teste

POST /webhooks/{id}/test coloca na fila um evento webhook.test para o endpoint, quaisquer que sejam os eventos que ele assina. Ele é assinado como qualquer outro evento, e o resultado aparece no log 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 testar na sua própria máquina, use um túnel que forneça uma URL HTTPS pública.

Pronto para integrar?

Solicite acesso à API e retornamos em 1 a 3 dias úteis.