# Webhooks > Events von HILLS Music an Ihrem HTTPS-Endpunkt: Abonnieren, die 13 Eventtypen, der Umschlag, X-Hills-Signature prüfen, Wiederholungen und Protokoll. Webhooks melden Ihrem System, wenn bei HILLS Music etwas passiert, sodass Sie nicht pollen müssen. HILLS sendet für jedes abonnierte Event einen signierten `POST` an Ihren HTTPS-Endpunkt. Webhooks sind zusammen mit der API Teil der Tarife Enterprise und Custom. ## Abonnieren Legen Sie einen Endpunkt mit `POST /webhooks` an (Scope `webhooks:write`). Sie können ihn auch in der HILLS Web-App unter **Settings → Webhooks** hinzufügen. ```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"]}' ``` Die Antwort (`201`) enthält den Endpunkt und sein Signing-Secret, `whsec_…`. **Das Secret wird nur einmal angezeigt.** Bewahren Sie es zusammen mit Ihren anderen Secrets auf. Die URL muss mit `https://` beginnen, Port 443 verwenden und öffentlich erreichbar sein. Private, Loopback-, Link-Local- und Cloud-Metadaten-Adressen werden mit `400 invalid_url` abgelehnt, sowohl beim Speichern der URL als auch bei jeder Anfrage. Sie können bis zu 20 Endpunkte haben (danach `409 too_many`). Alle Anfragen für Webhook-Endpunkte mit ihren Feldern und Fehlern finden Sie in der [Webhooks-Referenz](https://developer.hillsmusic.com/de/reference/webhooks/). `GET /webhooks/events` listet die Eventtypen mit ihrer Bedeutung auf. ## Events | Event | Gesendet, wenn | | --- | --- | | `release.submitted` | Ein Release wurde zur Prüfung eingereicht. | | `release.approved` | Ein Release wurde freigegeben und ist für seine Stores eingereiht. | | `release.changes_requested` | Ein Release wurde mit den nötigen Korrekturen zurückgegeben. | | `release.live` | Ein Release ist in seinem ersten Store live gegangen. | | `delivery.store_status_changed` | Der Status eines Releases in einem Store hat sich geändert: unterwegs, gesendet, live, Korrektur durch Sie nötig, Takedown angefordert oder entfernt. | | `takedown.completed` | Alle Stores eines freigegebenen Takedown-Antrags haben das Release entfernt. | | `ddex.import.report_ready` | Ein DDEX-Test ist abgeschlossen (der Ordner `/test`, die Web-App oder `POST /ddex/validate`); sein Bericht liegt vor. | | `ddex.import.imported` | Ein an Production gelieferter DDEX-Batch ist fertig importiert: done, partial oder failed. | | `statement.published` | Die Abrechnung eines Monats liegt vor (bei White-Label zuerst zu Ihrer Prüfung). | | `payout.status_changed` | Eine Auszahlungsanfrage wurde freigegeben, versendet, bezahlt, ist fehlgeschlagen oder wurde storniert. | | `request.status_changed` | HILLS Music hat eine Anfrage beantwortet: freigegeben, Änderungen nötig, abgeschlossen oder abgelehnt. | | `chat.message` | HILLS Music hat Ihnen im Live-Support geschrieben. | | `webhook.test` | Wird nur von `POST /webhooks/{id}/test` gesendet. Kann nicht abonniert werden. | ## Der Umschlag Jedes Event hat dieselbe äußere Struktur: ```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" } } } ``` | Feld | Bedeutung | | --- | --- | | `id` | Die UUID des Events. Bei jedem Versuch gleich: Deduplizieren Sie anhand dieses Werts. | | `type` | Der Eventtyp. | | `api_version` | `v1`. Innerhalb einer Version kommen Felder nur hinzu. | | `occurred_at` | Wann es passiert ist, ISO 8601 in UTC. | | `account_id` | Ihr Kundenkonto. | | `data` | Die Daten des Events. Ihre Struktur hängt von `type` ab. | Jede Anfrage enthält außerdem `X-Hills-Signature`, `X-Hills-Event` (den Typ), `X-Hills-Delivery` (die Zustellungs-ID, bei jedem Versuch gleich), `X-Hills-Attempt` (1, 2, 3 …), `Content-Type: application/json` und `User-Agent: HILLS-Webhooks/1.0`. Ein Body ist höchstens 256 KB groß. Ein größeres Event wird mit `data.truncated: true` und nur mit seinen IDs gesendet. Den Rest lesen Sie über die API. Die Reihenfolge der Events ist nicht garantiert. Verwenden Sie `occurred_at` und lesen Sie den aktuellen Zustand über die API, wenn es auf die Reihenfolge ankommt. ## Die Signatur prüfen `X-Hills-Signature` sieht so aus: `t=1792141500,v1=5257a869…`. So prüfen Sie sie: 1. Lesen Sie `t` (Unix-Sekunden) und `v1` aus dem Header. 2. Lehnen Sie die Anfrage ab, wenn `t` mehr als 5 Minuten von Ihrer Uhrzeit abweicht. Das verhindert Replay-Angriffe. 3. Berechnen Sie HMAC-SHA256 über `.`, mit Ihrem Secret unverändert als Schlüssel (einschließlich `whsec_`). Codieren Sie das Ergebnis hexadezimal. 4. Vergleichen Sie es in konstanter Zeit mit `v1`. Verwenden Sie die **unveränderten** Bytes des Bodys. Geparstes und neu serialisiertes JSON stimmt nicht überein. ```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); ``` ## Schnell antworten Jeder `2xx` innerhalb von 10 Sekunden gilt als Erfolg. Alles andere gilt als Fehlschlag, auch ein Timeout oder ein `3xx` (Weiterleitungen werden nicht verfolgt). Antworten Sie zuerst und erledigen Sie die Arbeit dann über eine Warteschlange. ## Wiederholungen Eine fehlgeschlagene Zustellung wird erneut versucht, insgesamt bis zu 8-mal. Nach jedem Fehlschlag folgt der nächste Versuch **1 Minute, 5 Minuten, 30 Minuten, 2 Stunden, 12 Stunden, 12 Stunden und 12 Stunden** später: insgesamt etwa anderthalb Tage. Nach dem 8. Fehlschlag wird die Zustellung aufgegeben, Sie können sie aber weiterhin [von Hand wiederholen](#the-delivery-log). ## Automatische Deaktivierung Ein Endpunkt, bei dem 20 Versuche in Folge ohne zwischenzeitlichen Erfolg fehlschlagen, wird deaktiviert. Sie erhalten eine Benachrichtigung. Zustellungen, die zu diesem Zeitpunkt noch warten, werden aufgegeben. `GET /webhooks/{id}` zeigt den Grund: `enabled: false`, `disabled_at` und `disabled_reason`. Beheben Sie das Problem an Ihrem Endpunkt und schalten Sie ihn dann wieder ein: ```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}' ``` Dadurch wird `failure_count` zurückgesetzt. Wiederholen Sie anschließend, was aufgegeben wurde. ## Das Zustellprotokoll `GET /webhooks/{id}/deliveries` listet auf, was in den letzten 30 Tagen gesendet wurde, das Neueste zuerst: Payload, Versuche, letzter HTTP-Status, Zeitpunkt und Fehler. Filtern Sie mit `status` (`pending`, `succeeded`, `failed`) und `event`. Die Paginierung erfolgt mit `before`. Siehe [Paginierung](https://developer.hillsmusic.com/de/guides/pagination/). So senden Sie eine Zustellung mit derselben Event-ID und Payload erneut: ```bash curl -X POST "https://api.hillsmusic.com/v1/webhooks/7/deliveries/1042/retry" \ -H "Authorization: Bearer $HILLS_API_KEY" ``` Wiederholung und Test antworten mit `409 endpoint_disabled`, solange der Endpunkt deaktiviert ist. ## Das Secret erneuern `POST /webhooks/{id}/roll-secret` erzeugt ein neues Secret. Die Antwort enthält es einmalig. **Das alte Secret ist sofort ungültig**, planen Sie den Wechsel also: 1. Lassen Sie Ihre Prüfung eine Liste von Secrets akzeptieren und jedes nacheinander ausprobieren. 2. Erneuern Sie das Secret und fügen Sie das neue der Liste hinzu. 3. Sobald Zustellungen mit dem neuen Secret die Prüfung bestehen, entfernen Sie das alte. Erneuern Sie das Secret sofort, wenn es offengelegt wurde. ## Test-Event senden `POST /webhooks/{id}/test` reiht ein `webhook.test`-Event für den Endpunkt ein, unabhängig davon, welche Events er abonniert hat. Es wird wie jedes andere Event signiert, und sein Ergebnis erscheint im Zustellprotokoll. ```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 ``` Zum Testen auf Ihrem eigenen Rechner verwenden Sie einen Tunnel, der Ihnen eine öffentliche HTTPS-URL bereitstellt. Quelle: https://developer.hillsmusic.com/de/guides/webhooks/