Webhooks
Webhooks ermöglichen es Ihnen, Echtzeit-Benachrichtigungen zu erhalten, wenn Ereignisse in Ihrem LabelGrid-Konto auftreten. Verwenden Sie Webhooks, um Workflows zu automatisieren und mit externen Systemen zu integrieren.
Für Entwickler: Sie können Webhooks auch programmatisch über die API verwalten. Siehe die LabelGrid API-Dokumentation für Endpunkte und Beispiele.
Wie Webhooks funktionieren
Abschnitt betitelt „Wie Webhooks funktionieren“- Sie konfigurieren einen Webhook - Geben Sie eine URL und die zu überwachenden Ereignisse an
- Ein Ereignis tritt ein - Zum Beispiel wird ein Release an einen Store geliefert
- LabelGrid sendet einen POST-Request - Ihr Server empfängt die Ereignisdaten
- Ihr System verarbeitet es - Automatisieren Sie Workflows basierend auf dem Ereignis
Auf Webhooks zugreifen
Abschnitt betitelt „Auf Webhooks zugreifen“- Klicken Sie auf Ihr Profilsymbol oben rechts
- Wählen Sie Webhooks aus dem Dropdown-Menü
Einen Webhook erstellen
Abschnitt betitelt „Einen Webhook erstellen“- Klicken Sie auf Create Webhook
- Geben Sie einen Name ein, um diesen Webhook zu identifizieren
- Geben Sie die URL ein, an die Sie Benachrichtigungen erhalten möchten
- Wählen Sie, welche Events diesen Webhook auslösen sollen
- Klicken Sie auf Create
Webhook Secret
Abschnitt betitelt „Webhook Secret“Wenn Sie einen Webhook erstellen, erhalten Sie einen Secret Key. Verwenden Sie diesen, um zu überprüfen, ob eingehende Anfragen tatsächlich von LabelGrid stammen:
- Speichern Sie das Secret sicher
- Überprüfen Sie die Signatur eingehender Anfragen
- Falls kompromittiert, generieren Sie das Secret neu
Verfügbare Ereignisse
Abschnitt betitelt „Verfügbare Ereignisse“Konfigurieren Sie Ihren Webhook für diese Ereignisse. Der Ereignis-Identifier ist der Wert, den Sie in der event-Eigenschaft des Payloads und im X-Webhook-Event-Header sehen werden:
| Ereignis-Identifier | Beschreibung |
|---|---|
delivery.completed | Ausgelöst, wenn ein Release erfolgreich an einen Store geliefert wurde |
delivery.failed | Ausgelöst, wenn die Lieferung an einen Store fehlschlägt |
takedown.completed | Ausgelöst, wenn ein Takedown-Request abgeschlossen ist |
release.review.status_changed | Ausgelöst, wenn sich der Review-Status eines Releases ändert |
release.preflight.report_ready | Ausgelöst, wenn der Preflight-QC-Bericht für ein zurückgehaltenes Release zum Abruf bereit ist |
stream_radar.flag_created | Ausgelöst, wenn eine Stream-Radar-Meldung erstellt wird oder eine aufgelöste Meldung bei einer neuen Erkennung erneut geöffnet wird |
stream_radar.flag_resolved | Ausgelöst, wenn eine Stream-Radar-Meldung aufgelöst wird, weil die Erkennungen aufgehört haben |
release.distributed | Ausgelöst, wenn ein Release verteilt wird |
payment.statement_ready | Ausgelöst, wenn eine Zahlungsabrechnung zur Ansicht bereit ist |
transcode.completed | Ausgelöst, wenn das Audio-Transcoding eines Tracks erfolgreich abgeschlossen wird |
transcode.failed | Ausgelöst, wenn das Transcoding eines Tracks fehlschlägt oder unvollständig endet |
distribution.outlet.status_changed | Ausgelöst bei jedem Statuswechsel des Vertriebs pro Outlet |
Sie können mehrere Ereignisse für einen einzelnen Webhook auswählen oder separate Webhooks für verschiedene Ereignistypen erstellen.
Sie können diese Liste auch programmatisch abrufen: GET /api/public/webhooks/event-types gibt jedes Ereignis zusammen mit einem data-Schema zurück, das dessen Payload-Schlüssel und -Typen beschreibt, sodass Consumer mit striktem Schema ihre eingehende Validierung vorab erweitern können.
Webhooks verwalten
Abschnitt betitelt „Webhooks verwalten“Ihre Webhooks anzeigen
Abschnitt betitelt „Ihre Webhooks anzeigen“Die Webhook-Liste zeigt:
| Spalte | Beschreibung |
|---|---|
| Name | Der von Ihnen zugewiesene Webhook-Name |
| URL | Wohin Benachrichtigungen gesendet werden |
| Events | Anzahl der konfigurierten Ereignisse |
| Status | Active oder Inactive |
| Success / Fail | Anzahl erfolgreicher und fehlgeschlagener Zustellungen |
| Last Triggered | Wann der Webhook zuletzt aufgerufen wurde |
Einen Webhook bearbeiten
Abschnitt betitelt „Einen Webhook bearbeiten“- Klicken Sie auf die Aktion Edit in der Webhook-Zeile
- Ändern Sie Name, URL oder Ereignisse
- Klicken Sie auf Save
Aktivieren / Deaktivieren
Abschnitt betitelt „Aktivieren / Deaktivieren“Schalten Sie den aktiven Status eines Webhooks um, ohne ihn zu löschen:
- Active - Webhook empfängt Benachrichtigungen
- Inactive - Webhook ist pausiert, keine Benachrichtigungen werden gesendet
Einen Webhook löschen
Abschnitt betitelt „Einen Webhook löschen“- Klicken Sie auf die Aktion Delete in der Webhook-Zeile
- Bestätigen Sie die Löschung
Webhooks testen
Abschnitt betitelt „Webhooks testen“Bevor Sie sich in der Produktion auf einen Webhook verlassen, testen Sie ihn:
- Klicken Sie auf die Aktion Test bei Ihrem Webhook
- LabelGrid sendet einen Test-Payload an Ihre URL
- Überprüfen Sie, ob Ihr Endpunkt ihn korrekt empfangen und verarbeitet hat
Webhook-Logs anzeigen
Abschnitt betitelt „Webhook-Logs anzeigen“Überwachen Sie Webhook-Aktivitäten und beheben Sie Probleme:
- Klicken Sie auf die Aktion View Logs bei einem Webhook
- Sehen Sie den Verlauf aller Webhook-Zustellungen
Log-Details
Abschnitt betitelt „Log-Details“Jeder Log-Eintrag zeigt:
| Feld | Beschreibung |
|---|---|
| Event Type | Welches Ereignis diese Zustellung ausgelöst hat |
| Response Status | HTTP-Statuscode von Ihrem Server |
| Duration | Wie lange die Anfrage gedauert hat |
| Attempt | Wiederholungsversuch-Nummer |
| Timestamp | Wann die Zustellung erfolgte |
Webhook-Payload-Format
Abschnitt betitelt „Webhook-Payload-Format“Wenn ein Ereignis eintritt, sendet LabelGrid einen POST-Request an Ihre URL mit einem JSON-Payload:
{ "event": "delivery.completed", "timestamp": "2026-05-05T10:00:00+00:00", "webhook_id": "123", "data": { // Ereignisspezifische Daten }}Das Feld timestamp verwendet das ISO-8601-Format. webhook_id ist die ID Ihres konfigurierten Webhooks (sie entspricht dem X-Webhook-Id-Header).
Event-Payloads
Abschnitt betitelt „Event-Payloads“Die Struktur des data-Objekts hängt vom event-Typ ab. Alle Feldtypen unten sind JSON-Typen, wie sie im Payload serialisiert werden.
delivery.completed
Abschnitt betitelt „delivery.completed“Wird einmal pro Outlet ausgelöst, wenn eine Release-Zustellung einen finalen Erfolgszustand erreicht.
{ "event": "delivery.completed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "label_id": 321, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "status": "complete" }}| Feld | Typ | Beschreibung |
|---|---|---|
distro_queue_id | integer | Interne Queue-ID für diesen Zustellversuch |
release_id | integer | Die Release, die zugestellt wurde |
label_id | integer | Das besitzende Label der Release, sodass Sie das Ereignis ohne zusätzlichen Lookup weiterleiten können |
release_cat | string | null | Ihre Release-Katalog-Referenz |
outlet_id | integer | null | Die Outlet-Ziel-ID |
outlet_name | string | null | Lesbarer Outlet-Name (z. B. "Spotify") |
status | string | Immer "complete" für dieses Ereignis |
delivery.failed
Abschnitt betitelt „delivery.failed“Wird einmal pro Outlet ausgelöst, wenn eine Release-Zustellung einen finalen Fehlerzustand erreicht. Gleicher Payload wie delivery.completed plus ein message-Feld.
{ "event": "delivery.failed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "label_id": 321, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "status": "error", "message": "Outlet rejected the delivery: missing ISRC." }}| Feld | Typ | Beschreibung |
|---|---|---|
status | string | Einer von error, fault, rejected, batch_exception |
message | string | null | Fehlergrund vom Outlet oder von der Distributions-Pipeline |
takedown.completed
Abschnitt betitelt „takedown.completed“Wird einmal pro Outlet ausgelöst, wenn eine Takedown-Anfrage erfolgreich ist. Gleiche Form wie delivery.completed plus ein takedown: true-Flag.
{ "event": "takedown.completed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "label_id": 321, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "status": "complete", "takedown": true }}release.distributed
Abschnitt betitelt „release.distributed“Wird einmal pro Release ausgelöst, wenn die Release in den Zustellzustand distributed wechselt. Wird nur beim Übergang zu distributed ausgelöst, nicht bei nachfolgenden Speicherungen, während die Release bereits distributed ist.
{ "event": "release.distributed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "label_id": 321, "release_cat": "ABC123", "release_title": "Summer EP", "delivery_status": "distributed" }}release.review.status_changed
Abschnitt betitelt „release.review.status_changed“Wird ausgelöst, wenn eine Release zwischen Pruefungszustaenden wechselt.
{ "event": "release.review.status_changed", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "label_id": 321, "release_cat": "ABC123", "release_title": "Summer EP", "previous_status": "to_review", "new_status": "approved" }}| Feld | Typ | Beschreibung |
|---|---|---|
previous_status | string | Vorheriger Status. Einer von draft, to_review, approved, rejected, require_changes, audit |
new_status | string | Neuer Status. Gleicher Wertesatz |
review_issues | array (optional) | Nur bei Übergängen zu require_changes und rejected vorhanden: die Probleme, die Ihre Aufmerksamkeit erfordern. Bei jedem anderen Übergang fehlt der Schlüssel, gehen Sie also nicht davon aus, dass er immer vorhanden ist |
release.preflight.report_ready
Abschnitt betitelt „release.preflight.report_ready“Wird ausgelöst, wenn der Qualitätsbericht von Preflight QC für ein Release in der Wartephase vor der Prüfung zum Abruf bereit ist. Erfordert die Preflight-QC-Zusatzoption in Ihrem Konto.
{ "event": "release.preflight.report_ready", "timestamp": "2026-07-07T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "label_id": 321, "release_cat": "ABC123", "release_title": "Summer EP", "generated_at": "2026-07-07T09:58:12+00:00", "profile": { "name": "quality_report", "version": 2 }, "counts": { "blocking": 1, "informational": 2, "requires_feedback": 1 } }}| Feld | Typ | Beschreibung |
|---|---|---|
release_id | integer | Das Release, zu dem der Bericht gehört |
label_id | integer | Das besitzende Label der Release |
release_cat | string | null | Ihre Release-Katalog-Referenz |
release_title | string | null | Der Release-Titel |
generated_at | string | Wann die Prüfungen abgeschlossen wurden (ISO 8601). Entspricht report.generated_at des Qualitätsbericht-Endpunkts |
profile | object | Das Qualitätsprofil, über das die Zählungen berechnet wurden: {name, version} |
counts | object | Nur aggregierte Zählungen: {blocking, informational, requires_feedback}. Die Zählung requires_feedback überschneidet sich mit den beiden anderen |
stream_radar.flag_created
Abschnitt betitelt „stream_radar.flag_created“Ausgelöst, wenn eine Stream-Radar-Meldung erstellt wird – entweder eine ganz neue Meldung oder eine zuvor aufgelöste Meldung, die bei einer neuen Erkennung erneut geöffnet wird. Erfordert die Stream-Radar-Zusatzoption in Ihrem Konto. Das Feld transition unterscheidet die beiden Fälle: published für eine neue Meldung, reopened für eine wieder aktiv gewordene Meldung.
{ "event": "stream_radar.flag_created", "timestamp": "2026-07-07T10:00:00+00:00", "webhook_id": "123", "data": { "flag_id": 4501, "dsp": "spotify", "isrc": "USRC12345678", "release_id": 789, "track_id": 654, "severity": "high", "status": "active", "transition": "published", "first_detected_at": "2026-07-06T00:00:00+00:00", "last_detected_at": "2026-07-07T00:00:00+00:00", "estimated_affected_streams": 12500, "published_at": "2026-07-07T09:58:12+00:00", "resolved_at": null }}| Feld | Typ | Beschreibung |
|---|---|---|
flag_id | integer | Die stabile Kennung der Meldung; entspricht id auf den Stream-Radar-Endpunkten |
dsp | string | Die Plattform, auf der das Muster beobachtet wurde (z. B. spotify) |
isrc | string | Der ISRC der betroffenen Aufnahme |
release_id | integer | Das Release, zu dem die Aufnahme gehört |
track_id | integer | null | Der konkrete Track, wenn der ISRC eindeutig einem Ihrer Tracks zugeordnet ist |
severity | string | low, medium oder high |
status | string | active für dieses Ereignis |
transition | string | published für eine neue Meldung, reopened, wenn eine aufgelöste Meldung wieder aktiv geworden ist |
first_detected_at | string | null | Wann das Muster für diesen Track und diese Plattform erstmals beobachtet wurde (ISO 8601) |
last_detected_at | string | null | Die jüngste Erkennung (ISO 8601) |
estimated_affected_streams | integer | null | Eine Schätzung, wie viele Streams beteiligt sind |
published_at | string | Wann Ihnen die Meldung erstmals gemeldet wurde (ISO 8601) |
resolved_at | string | null | null, solange die Meldung aktiv ist |
stream_radar.flag_resolved
Abschnitt betitelt „stream_radar.flag_resolved“Ausgelöst, wenn eine Stream-Radar-Meldung aufgelöst wird, weil die Erkennungen aufgehört haben. Erfordert die Stream-Radar-Zusatzoption. Dieselben Felder wie stream_radar.flag_created (ohne transition), mit status auf resolved und gesetztem resolved_at.
{ "event": "stream_radar.flag_resolved", "timestamp": "2026-07-14T10:00:00+00:00", "webhook_id": "123", "data": { "flag_id": 4501, "dsp": "spotify", "isrc": "USRC12345678", "release_id": 789, "track_id": 654, "severity": "high", "status": "resolved", "first_detected_at": "2026-07-06T00:00:00+00:00", "last_detected_at": "2026-07-12T00:00:00+00:00", "estimated_affected_streams": 18700, "published_at": "2026-07-07T09:58:12+00:00", "resolved_at": "2026-07-14T09:55:03+00:00" }}payment.statement_ready
Abschnitt betitelt „payment.statement_ready“Wird ausgelöst, wenn eine Zahlungsabrechnung generiert und zur Ansicht bereit ist.
{ "event": "payment.statement_ready", "timestamp": "2026-05-18T10:00:00+00:00", "webhook_id": "123", "data": { "payment_request_id": 1024, "invoice_number": "INV-2026-001", "period": "2026-04-30", "amount": 1234.56, "total_due_usd": 1234.56, "currency": "USD" }}| Feld | Typ | Beschreibung |
|---|---|---|
payment_request_id | integer | Interne Zahlungsanforderungs-ID |
invoice_number | string | Rechnungsreferenz für die Abrechnung |
period | string | null | Ende-des-Zeitraums-Datum (ISO-8601-Datum, YYYY-MM-DD) |
amount | number | Abrechnungsbetrag in der Währung currency |
total_due_usd | number | Abrechnungssumme umgerechnet in USD |
currency | string | ISO-4217-Waehrungscode (Standard USD) |
transcode.completed und transcode.failed
Abschnitt betitelt „transcode.completed und transcode.failed“Wird ausgelöst, wenn das Audio-Transcoding eines Tracks abgeschlossen wird. transcode.completed wird bei Erfolg ausgelöst; transcode.failed wird ausgelöst, wenn das Transcoding fehlschlägt oder unvollständig endet. Beide haben dieselbe Payload-Form.
{ "event": "transcode.completed", "timestamp": "2026-07-07T10:00:00+00:00", "webhook_id": "123", "data": { "release_id": 789, "label_id": 321, "track_id": 654, "transcoder_queue_id": 987, "status": "complete", "status_message": "transcode_complete", "files": [ { "asset_type_id": 2, "status": "complete" } ] }}| Feld | Typ | Beschreibung |
|---|---|---|
release_id | integer | Das Release, zu dem der Track gehört |
label_id | integer | Das besitzende Label der Release |
track_id | integer | Der Track, der transcodiert wurde |
transcoder_queue_id | integer | Interne Transcoder-Queue-ID |
status | string | Roher Queue-Status: complete, error oder incomplete |
status_message | string | Sicherer, aufgezählter Grundcode: transcode_complete, transcode_error oder transcode_incomplete |
files | array | Details pro Datei für den Track: {asset_type_id, status} je transcodierter Datei |
distribution.outlet.status_changed
Abschnitt betitelt „distribution.outlet.status_changed“Wird bei jedem Statuswechsel des Vertriebs pro Outlet ausgelöst (zum Beispiel scheduled → transcoding → batched → complete), nicht nur bei den finalen, die von delivery.completed, delivery.failed und takedown.completed abgedeckt werden. Dieses Ereignis ist absichtlich gesprächig: Abonnieren Sie es nur, wenn Sie den vollständigen Verlauf pro Outlet wünschen.
{ "event": "distribution.outlet.status_changed", "timestamp": "2026-07-07T10:00:00+00:00", "webhook_id": "123", "data": { "distro_queue_id": 456, "release_id": 789, "label_id": 321, "release_cat": "ABC123", "outlet_id": 12, "outlet_name": "Spotify", "previous_status": "transcoding", "status": "batched" }}| Feld | Typ | Beschreibung |
|---|---|---|
distro_queue_id | integer | Interne Queue-ID für diese Zustellung |
release_id | integer | Das Release, das vertrieben wird |
label_id | integer | Das besitzende Label der Release |
release_cat | string | null | Ihre Release-Katalog-Referenz |
outlet_id | integer | null | Die Outlet-Ziel-ID |
outlet_name | string | null | Lesbarer Outlet-Name |
previous_status | string | null | Der vorherige Status; null, wenn die Zeile keinen erkannten vorherigen Status hatte |
status | string | Der neue Status |
Webhook-Signaturen verifizieren
Abschnitt betitelt „Webhook-Signaturen verifizieren“Jede Webhook-Zustellung wird signiert, damit Sie verifizieren können, dass sie tatsächlich von LabelGrid stammt. Verifizieren Sie die Signatur immer, bevor Sie das Ereignis verarbeiten.
Request-Header
Abschnitt betitelt „Request-Header“Jeder Webhook-POST-Request enthält diese Header:
| Header | Beschreibung |
|---|---|
X-Webhook-Signature | HMAC-SHA256 des Roh-Request-Bodys, in Kleinbuchstaben-Hexadezimal, ohne Algorithmus-Praefix |
X-Webhook-Timestamp | Praktische Kopie der timestamp-Eigenschaft im Body. Nicht von der Signatur abgedeckt — entscheiden Sie damit niemals, ob eine Zustellung aktuell ist. |
X-Webhook-Event | Ereignis-Identifier (z. B. delivery.completed) |
X-Webhook-Id | Die ID der Webhook-Konfiguration, die die Zustellung empfängt (keine ID der einzelnen Zustellung) |
User-Agent | LabelGrid-Webhooks/1.0 |
Content-Type | application/json |
Algorithmus
Abschnitt betitelt „Algorithmus“- Algorithmus: HMAC-SHA256
- Encoding: Hexadezimal in Kleinbuchstaben
- Präfix: Keiner — der Wert ist nur der Hex-Digest, nicht
sha256=... - Signierter Inhalt: Der vollständige rohe JSON-Request-Body — und sonst nichts. Kein Header wird signiert.
Der Body enthält seine eigene timestamp-Eigenschaft, dieser Wert ist also durch die Signatur geschützt. Der Header X-Webhook-Timestamp ist lediglich eine Kopie davon, die der Bequemlichkeit halber mitgeschickt wird; wer eine Zustellung abfängt, kann den Header beliebig ändern, ohne die Signatur ungültig zu machen. Aktualitätsprüfungen müssen timestamp deshalb aus dem geparsten Body lesen, niemals aus dem Header.
Verifizierungs-Rezept
Abschnitt betitelt „Verifizierungs-Rezept“- Lesen Sie den Roh-Request-Body, bevor JSON geparst oder transformiert wird. Eine Re-Serialisierung des geparsten JSON kann andere Bytes erzeugen und die Signatur ungültig machen.
- Berechnen Sie
HMAC-SHA256(roh_body, ihr_webhook_secret)und nehmen Sie den Hex-Digest in Kleinbuchstaben. - Vergleichen Sie mit
X-Webhook-Signaturemithilfe eines konstanten Zeitvergleichs. Stoppen Sie hier, wenn er nicht übereinstimmt. - Erst jetzt parsen Sie den Body und lehnen den Request ab, wenn dessen
timestamp-Eigenschaft älter als Ihr Replay-Toleranzfenster ist — wir empfehlen 5 Minuten. Weil dieser Wert signiert ist, kann ein Angreifer ihn nicht auffrischen, um eine abgefangene Zustellung aktuell erscheinen zu lassen.
Jeder Wiederholungsversuch wird erneut mit einem neuen timestamp signiert, sodass ein 5-Minuten-Fenster niemals eine legitime Wiederholung ablehnt, wie spät im Backoff-Plan sie auch eintrifft.
PHP-Beispiel
Abschnitt betitelt „PHP-Beispiel“$rawBody = file_get_contents('php://input');$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $rawBody, $webhookSecret);
if (! hash_equals($expected, $signature)) { http_response_code(401); exit('Ungültige Signatur');}
// Erst parsen, nachdem die Bytes als authentisch nachgewiesen sind.$payload = json_decode($rawBody, true);
// Die Aktualität stammt aus dem SIGNIERTEN Zeitstempel im Body,// niemals aus dem Header X-Webhook-Timestamp.if (! isset($payload['timestamp']) || abs(time() - strtotime($payload['timestamp'])) > 300) { http_response_code(401); exit('Veraltete Zustellung');}
// ... Ereignis verarbeiten (siehe „Wiederholte Zustellungen verarbeiten“ unten)http_response_code(200);Node.js-Beispiel
Abschnitt betitelt „Node.js-Beispiel“const crypto = require('crypto');
// Express: Roh-Body VOR jeglicher JSON-Middleware erfassenapp.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => { const rawBody = req.body; // Buffer const signature = req.header('X-Webhook-Signature') || '';
const expected = crypto .createHmac('sha256', webhookSecret) .update(rawBody) .digest('hex');
const sigBuf = Buffer.from(signature, 'hex'); const expBuf = Buffer.from(expected, 'hex');
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) { return res.status(401).send('Ungültige Signatur'); }
// Erst parsen, nachdem die Bytes als authentisch nachgewiesen sind. const payload = JSON.parse(rawBody.toString('utf8'));
// Die Aktualität stammt aus dem SIGNIERTEN Zeitstempel im Body, // niemals aus dem Header X-Webhook-Timestamp. const sentAt = new Date(payload.timestamp).getTime();
if (Number.isNaN(sentAt) || Math.abs(Date.now() - sentAt) > 5 * 60 * 1000) { return res.status(401).send('Veraltete Zustellung'); }
// ... Ereignis verarbeiten (siehe „Wiederholte Zustellungen verarbeiten“ unten) res.sendStatus(200);});Python-Beispiel
Abschnitt betitelt „Python-Beispiel“import hmac, hashlib, jsonfrom datetime import datetime, timezone
raw_body = request.get_data() # Flask: bytes, vor jeglichem JSON-Parsingsignature = request.headers.get('X-Webhook-Signature', '')
expected = hmac.new( webhook_secret.encode('utf-8'), raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature): return ('Ungültige Signatur', 401)
payload = json.loads(raw_body) # erst parsen, wenn die Bytes echt sind
delivery_time = datetime.fromisoformat(payload['timestamp']) # der SIGNIERTE Wert, nie der Headerif abs((datetime.now(timezone.utc) - delivery_time).total_seconds()) > 300: return ('Veraltete Zustellung', 401)
return ('', 200) # Ereignis verarbeiten, dann bestätigenHäufige Fallstricke
Abschnitt betitelt „Häufige Fallstricke“- Aktualitätsprüfung anhand des Headers
X-Webhook-Timestamp. Der Header wird nicht signiert. Wer eine Zustellung abfängt, kann denselben Body mit derselben Signatur und einem frischen Header-Wert erneut senden und eine header-basierte Prüfung dauerhaft bestehen. Lesen Sietimestampstattdessen aus dem geparsten Body — dieser Wert ist signiert. - Re-Serialisieren des Bodys vor dem Hashing. Frameworks, die JSON automatisch parsen (Express
express.json(), Laravels Standard-Request-Body), verlieren die Original-Bytes. Erfassen Sie zuerst den Roh-Body. - Verwenden eines nicht zeitkonstanten Vergleichs (
==,===). Anfällig für Timing-Angriffe — verwenden Sie immerhash_equals(PHP),crypto.timingSafeEqual(Node),hmac.compare_digest(Python) oder das Äquivalent Ihrer Sprache. - Erwarten eines
sha256=-Präfix. Der Header-Wert ist nur der Hex-Digest ohne Präfix. - Überspringen der Aktualitätsprüfung. Ohne sie kann eine abgefangene Zustellung unbegrenzt gegen Ihren Endpunkt wiederholt werden.
X-Webhook-Idals Zustellungs-ID betrachten. Sie identifiziert die Webhook-Konfiguration, nicht die einzelne Zustellung, und ist ebenfalls nicht signiert.
Wiederholte Zustellungen verarbeiten
Abschnitt betitelt „Wiederholte Zustellungen verarbeiten“Die Webhook-Zustellung erfolgt mindestens einmal: Eine Zustellung, die Ihr Endpunkt tatsächlich verarbeitet hat, kann erneut eintreffen, wenn Ihre 2xx-Antwort verloren ging oder erst nach dem 10-Sekunden-Timeout ankam und LabelGrid sie daraufhin wiederholt. Die Signaturprüfung belegt, dass eine Anfrage echt ist — sie belegt nicht, dass Sie sie noch nicht verarbeitet haben.
Zustellungen tragen keine eindeutige ID pro Zustellung; bilden Sie deshalb einen eigenen Idempotenzschlüssel aus dem signierten Payload. Der Ereignistyp plus die Bezeichner in data genügen in der Regel — zum Beispiel delivery.completed plus distro_queue_id oder transcode.completed plus track_id. Halten Sie den Schlüssel fest, wenn Sie ein Ereignis verarbeiten, und ignorieren Sie alles, was Sie bereits festgehalten haben.
Verwenden Sie dafür weder die Signatur noch den timestamp. Jeder Versuch wird im Moment des Versands neu signiert, sodass die Wiederholung eines bereits verarbeiteten Ereignisses mit einem anderen timestamp und einer anderen Signatur eintrifft — der Schlüssel muss aus den Bezeichnern des Ereignisses selbst stammen.
Kombinieren Sie das mit der Aktualitätsprüfung oben: Die Aktualität begrenzt, wie lange eine abgefangene Zustellung wiederholbar bleibt, und Idempotenz macht eine Wiederholung harmlos — ob sie nun von einem Wiederholungsversuch oder von einem Angreifer innerhalb des Fensters stammt.
Limits und Zuverlässigkeit
Abschnitt betitelt „Limits und Zuverlässigkeit“Request-Limits
Abschnitt betitelt „Request-Limits“| Limit | Wert |
|---|---|
| Request-Timeout | 10 Sekunden |
| Maximale Payload-Größe | 64 KB |
| Maximale Anzahl Webhooks pro Benutzer | 10 |
Wenn Ihr Endpunkt nicht innerhalb von 10 Sekunden antwortet, wird die Zustellung als Fehler behandelt und wiederholt.
Wiederholungsplan
Abschnitt betitelt „Wiederholungsplan“Wenn Ihr Endpunkt einen Non-2xx-Status zurückgibt oder einen Timeout hat, wiederholt LabelGrid mit exponentiellem Backoff:
| Versuch | Wartezeit vor Wiederholung |
|---|---|
| 1 → 2 | 30 Sekunden |
| 2 → 3 | 1 Minute |
| 3 → 4 | 2 Minuten |
| 4 → 5 | 4 Minuten |
| 5 → 6 | 8 Minuten |
| 6 → 7 | 16 Minuten |
| 7 → 8 | 32 Minuten |
| 8 → 9 | 64 Minuten |
| 9 → 10 | 128 Minuten |
Jedes Intervall enthält 0–30 Sekunden Jitter. Nach 10 Versuchen (~4,5 Stunden Gesamtdauer) wird die Zustellung als dauerhaft fehlgeschlagen protokolliert und nicht weiter wiederholt.
Automatische Deaktivierung
Abschnitt betitelt „Automatische Deaktivierung“Wenn der Endpunkt eines Webhooks wiederholt fehlschlägt — mehrere fehlgeschlagene Zustellungen ohne eine einzige erfolgreiche dazwischen —, deaktiviert LabelGrid den Webhook automatisch, um einen Endpunkt nicht weiter anzusteuern, der offensichtlich keine Ereignisse empfangen kann. Der Fehlerzaehler wird bei jeder erfolgreichen Zustellung zurückgesetzt, sodass ein gelegentlicher Aussetzer einen Webhook nie deaktiviert; nur anhaltendes, ununterbrochenes Fehlschlagen tut dies.
Wenn ein Webhook auf diese Weise deaktiviert wird, erhält sein Eigentümer eine E-Mail. Die E-Mail nennt den Namen des Webhooks und die URL seines Endpunkts sowie die Art des Fehlers, der die Deaktivierung ausgelöst hat — zum Beispiel eine Zeitüberschreitung der Verbindung oder wiederholte HTTP-Fehler.
Die Reaktivierung erfolgt im Selfservice: Beheben Sie Ihren Endpunkt und schalten Sie den Webhook anschließend unter Profil → Webhooks wieder ein. Die Reaktivierung eines deaktivierten Webhooks setzt seinen Fehlerzaehler zurück. Die Webhook-Liste zeigt den aktiven Status und den aktuellen Fehlerzaehler jedes Webhooks, sodass Sie einen fehlerhaften Endpunkt auf einen Blick erkennen.
Best Practices für Zuverlässigkeit
Abschnitt betitelt „Best Practices für Zuverlässigkeit“- Geben Sie schnell eine 2xx-Antwort zurück (innerhalb von 10 Sekunden)
- Verarbeiten Sie die Daten asynchron nach der Bestätigung
- Verifizieren Sie die Signatur bei jeder Anfrage (siehe Webhook-Signaturen verifizieren)
- Machen Sie Ihren Handler idempotent — die Zustellung erfolgt mindestens einmal (siehe Wiederholte Zustellungen verarbeiten)
- Überwachen Sie Ihren Fehlerzaehler in der Webhook-Liste
- Prüfen Sie die Zustell-Logs, wenn Sie verpasste Ereignisse untersuchen
Anwendungsfälle
Abschnitt betitelt „Anwendungsfälle“Automatisierte Benachrichtigungen
Abschnitt betitelt „Automatisierte Benachrichtigungen“- Slack-Nachrichten senden, wenn Releases live gehen
- Ihr Team per E-Mail benachrichtigen, wenn Lieferungen fehlschlagen
- Interne Dashboards aktualisieren
Workflow-Automatisierung
Abschnitt betitelt „Workflow-Automatisierung“- Marketing-Kampagnen auslösen, wenn Releases verteilt werden
- Ihre Website aktualisieren, wenn neue Inhalte verfügbar sind
- Status mit externen Projektmanagement-Tools synchronisieren
Überwachung und Alarmierung
Abschnitt betitelt „Überwachung und Alarmierung“- Sofortige Benachrichtigungen bei Lieferfehlern erhalten
- Vertriebsfortschritt in Echtzeit verfolgen
- Review-Status-Änderungen überwachen
Fehlerbehebung
Abschnitt betitelt „Fehlerbehebung“Webhook empfängt keine Ereignisse
Abschnitt betitelt „Webhook empfängt keine Ereignisse“- Status prüfen - Ist der Webhook Active?
- URL verifizieren - Ist der Endpunkt aus dem Internet erreichbar?
- Ereignisse prüfen - Sind die richtigen Ereignisse ausgewählt?
- Logs überprüfen - Sind Fehler aufgezeichnet?
Hohe Fehlerzahl
Abschnitt betitelt „Hohe Fehlerzahl“- Ihren Endpunkt prüfen - Gibt er 200 OK zurück?
- Antwortzeit prüfen - Antwortet er innerhalb des Timeouts?
- Fehlermeldungen überprüfen - Was schlägt fehl?
- Manuell testen - Einen Test-Webhook senden
Secret neu generieren
Abschnitt betitelt „Secret neu generieren“Wenn Ihr Webhook-Secret kompromittiert ist:
- Klicken Sie auf Regenerate Secret in den Webhook-Einstellungen
- Aktualisieren Sie Ihre Anwendung mit dem neuen Secret
- Das alte Secret funktioniert sofort nicht mehr
Brauchen Sie Hilfe?
Abschnitt betitelt „Brauchen Sie Hilfe?“Wenn Sie Fragen zu Webhooks haben, kontaktieren Sie unser Support-Team.
Sie nutzen LabelGrid noch nicht?
Alles, was Sie gerade gelesen haben, steht Ihnen auf unserer Plattform zur Verfügung.
Entdecken Sie, was LabelGrid kann →