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:
- Leia
t(segundos Unix) ev1no header. - Recuse a requisição se
testiver a mais de 5 minutos do seu relógio. Isso impede ataques de replay. - Calcule o HMAC-SHA256 de
<t>.<raw body>, usando o seu segredo exatamente como está (incluindowhsec_) como chave. Codifique o resultado em hexadecimal. - Compare-o com
v1em 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:
- Faça o seu verificador aceitar uma lista de segredos: teste um de cada vez.
- Renove o segredo e adicione o novo à lista.
- 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.