Wählen Sie Auto, TokenLab Verified oder Official für jede Anfrage, wobei die Preise vorab angezeigt werden. Neuigkeiten ansehen

Async AI Task Webhooks: Die Signatur verifizieren und anschließend die Aufgabe lesen

CryptoCrypto
·28. September 2026·12 Min. Lesezeit·Aktualisiert 28. September 2026·25 Aufrufe
#Webhooks#Asynchrone Aufgaben#API-Integration#Sicherheit
Async AI Task Webhooks: Die Signatur verifizieren und anschließend die Aufgabe lesen

Ein Webhook ist ein signierter Hinweis darauf, dass ein Task einen Endzustand erreicht hat. Er ist nicht der Datensatz selbst. Die Regel ist daher kurz: Verifizieren Sie die Rohdaten (Raw Bytes), deduplizieren Sie anhand der Event-ID, antworten Sie schnell mit 2xx und lesen Sie dann GET /v1/tasks/{id} für das Ergebnis und den Abrechnungsstatus.

Workspace-Task-Webhooks wurden am 27.09.2026 eingeführt. Sie erhalten eine Management API für den Webhook-Lebenszyklus, Test-Zustellungen, Secret-Rotation und Zustellungshistorie. Dashboard- und MCP-Verwaltung sind ebenfalls verfügbar.

Eine Korrektur vorab: Unser früherer Leitfaden für asynchrone Bildgenerierungs-Webhooks besagte, dass TokenLab keinen Task-Callback hätte; das war vor dem 27.09.2026 korrekt, und dieser Leitfaden wurde zusammen mit diesem hier aktualisiert.

Webhooks oder Polling? Nutzen Sie beides

Sie lösen unterschiedliche Probleme, und keines ersetzt das andere.

Situation Greifen Sie zu
Sie möchten reagieren, sobald ein Task endet Webhook
Sie benötigen das maßgebliche Ergebnis oder die Kosten GET /v1/tasks/{id}
Ihr Empfänger war eine Weile offline Polling mit gespeicherten Task-IDs
Sie möchten ein Fallback, falls Zustellungen verloren gehen Polling in langsamen Intervallen

Webhooks machen Statusabfragen nicht überflüssig und fügen kein Polling-Limit hinzu. Behalten Sie beides bei. Selbst mit aktiven Webhooks ist eine langsame Abgleichschleife, die Ihre gespeicherten Task-IDs liest, eine günstige Absicherung.

Wenn Sie pollen, verwenden Sie poll_url, warten Sie ab, während der Task aussteht, und stoppen Sie bei Endzuständen. Stoppen Sie bei 401, 403, 404 oder wenn error.retryable == false ist. Wiederholen Sie 503 async_task_owner_unavailable mit Backoff. Ein fehlender oder abgelaufener Task gibt 404 async_task_not_found zurück. Siehe den Leitfaden für asynchrone Jobs und Polling für den Polling-Vertrag.

Drei Anmeldedaten, drei Aufgaben

Diese zu vermischen ist der schnellste Weg zu einem defekten Empfänger.

Anmeldedaten Präfix Funktion Hinweise
Management Token mt-… Erstellt, listet, aktualisiert, löscht, testet und rotiert Webhooks unter /v1/management/webhooks* Wird als Authorization: Bearer mt-… gesendet. Workspace-gebunden
API Key sk-… Übermittelt Modellanfragen und liest Task-Status via GET /v1/tasks/{id} Wird von der Management API abgelehnt
Signing Secret whsec_… Verifiziert Zustellungen bei Ihrem Empfänger Niemals ein Bearer-Token

Zwei Dinge zum Management Token: Erstens autorisiert es auch andere Workspace-Management-Operationen, es ist also kein reines Webhook-Credential. Wählen Sie denselben Workspace wie für den API Key, der Ihre Tasks übermittelt. Zweitens erstellen Sie es unter Dashboard → API → Management Tokens. Siehe ein weiteres Beispiel für die Management API.

Bewahren Sie mt-… und whsec_… nur auf Ihrem Backend auf. Versenden Sie keines davon an einen Browser oder einen mobilen Client.

Endpoint erstellen und Secret sofort speichern

Der Erstellungsaufruf gibt 201 mit der Webhook-id und einem einmaligen secret zurück, das mit whsec_… beginnt. Listen, Abrufen und Aktualisieren zeigen dieses Secret nie wieder an. Speichern Sie es in dem Moment, in dem Sie es sehen.

export TOKENLAB_MANAGEMENT_TOKEN="mt-your-management-token"
curl https://api.tokenlab.sh/v1/management/webhooks \
  -H "Authorization: Bearer $TOKENLAB_MANAGEMENT_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"url":"https://your-app.example/webhooks/tokenlab","events":["task.completed","task.failed","task.timeout"],"description":"Production task results"}'

Dieselben Endpoints können auf drei Arten verwaltet werden, und alle drei bearbeiten dieselben Objekte:

Die URL-Regeln sind streng. Der Endpoint muss ein öffentliches HTTPS sein. Keine Anmeldedaten, Query-Strings oder Fragmente in der URL. Redirects werden nicht gefolgt, daher zählt ein 301 als fehlgeschlagene Zustellung.

Sie können bis zu 10 Endpoints pro Workspace haben. Ein 11. Erstellungsversuch gibt 409 webhook_limit_reached zurück.

Methode Pfad Zweck
GET /v1/management/webhooks Endpoints auflisten
POST /v1/management/webhooks Endpoint erstellen
GET /v1/management/webhooks/{webhookId} Einen Endpoint lesen
PATCH /v1/management/webhooks/{webhookId} Aktualisieren, pausieren oder fortsetzen
DELETE /v1/management/webhooks/{webhookId} Löschen
POST /v1/management/webhooks/{webhookId}/rotate-secret Signing Secret rotieren
POST /v1/management/webhooks/{webhookId}/test Einen webhook.test senden
GET /v1/management/webhooks/{webhookId}/deliveries?page=1&limit=50 Zustellungshistorie, Limit bis zu 100

Pausieren mit PATCH {"is_active": false}. Fortsetzen mit PATCH {"is_active": true}. Das Fortsetzen setzt den Zähler für aufeinanderfolgende Fehler zurück, was nach einem Ausfall wichtig ist.

Was tatsächlich ankommt

Jede Zustellung ist ein POST mit einem JSON-Envelope. Felder der Management API sind snake_case, aber Callback-Felder sind camelCase. Gehen Sie nicht davon aus, dass eine Schreibweise auf die andere übertragbar ist.

Feld Bedeutung
id Event-ID. Verwenden Sie diese zur Deduplizierung
type Event-Typ
created Unix-Sekunden
data Event-Payload, Form hängt vom Event ab
Event Wird ausgelöst bei
task.completed Task erfolgreich beendet
task.failed Task mit Fehler beendet
task.timeout Task hat Zeitlimit erreicht
webhook.test Wird nur durch die Test-Operation gesendet

task.completed enthält taskType (z. B. video oder image), taskId, ein optionales model, durationMs, resultUrls und settledCost.

task.failed enthält taskType, taskId, error, errorCode, retryable und refundOutcome.

task.timeout enthält taskType, taskId, refundOutcome und Wartezeit-Felder. Lesen Sie den Task-Datensatz für diese Werte; der Feldsatz hängt vom Task ab.

Abonnements decken zukünftige Endzustands-Events für asynchrone Tasks im Workspace ab. Synchrone Ergebnisse und historische Tasks werden nicht erneut gesendet. Sie erhalten jeden Workspace-Task für die von Ihnen gewählten Event-Typen, gleichen Sie also data.taskId mit der ID ab, die Sie beim Erstellen des Tasks gespeichert haben.

Felder können je nach Task fehlen. Deshalb bleibt GET /v1/tasks/{id} mit dem sk-…-Key des ursprünglichen Workspaces die Quelle der Wahrheit für das Ergebnis und den Abrechnungsstatus. Das Event teilt Ihnen mit, dass etwas fertig ist. Der Task-Datensatz teilt Ihnen mit, was produziert wurde und was es gekostet hat.

Noch etwas zu retryable bei einem Fehler-Event: Es beschreibt den Fehler bei der Generierung, nicht eine Anweisung zur automatischen erneuten Übermittlung. Eine neue Übermittlung ist ein neuer abrechenbarer Task.

Rohdaten verifizieren, dann einmalig verarbeiten

Jeder POST enthält drei Header:

  • X-Webhook-ID
  • X-Webhook-Timestamp, Unix-Sekunden
  • X-Webhook-Signature, formatiert als sha256=

Die Signatur ist HMAC-SHA256 über den exakten Zeitstempel-String, einen Punkt und die rohen Body-Bytes der Anfrage, verschlüsselt mit dem vollständigen whsec_…-Secret. Die Reihenfolge ist wichtig, ebenso wie der Body.

Zwei Fehler machen Signaturprüfungen häufiger kaputt als alles andere:

  1. Verifizierung von geparstem JSON. Wenn Sie den Body parsen und neu serialisieren, ändern sich die Bytes und der HMAC stimmt nicht mehr überein. Lesen Sie den rohen Body. Behalten Sie ihn als Bytes bei, bis die Verifizierung erfolgreich ist.
  2. Verifizierung mit nur einem Secret während der Rotation. Nach der Rotation können Zustellungen, die bereits unterwegs sind, noch die vorherige Signatur tragen. Akzeptieren Sie für ein kurzes Zeitfenster eine Liste von Secrets.

Der Node-Empfänger unten ist frei von Abhängigkeiten und verwendet node:http. Er liest den rohen Body, verifiziert gegen eine Liste von Secrets, prüft das 300-Sekunden-Fenster, vergleicht die Body-id mit X-Webhook-ID, dedupliziert nach Event-ID, stellt in die Warteschlange und gibt 204 zurück. Die Deduplizierung im Beispiel ist ein In-Memory-Set; verwenden Sie in der Produktion eine eindeutige Datenbank-Constraint.

import { createServer } from 'node:http';
import { createHmac, timingSafeEqual } from 'node:crypto';

// Während der Rotation beide Secrets auflisten: das neue und das vorherige whsec_.
const SECRETS = (process.env.TOKENLAB_WEBHOOK_SECRETS ?? '').split(',').filter(Boolean);
const TOLERANCE_SECONDS = 300;
const seen = new Set(); // In Produktion eindeutige DB-Constraint verwenden, nicht Memory.

function verify(rawBody, headers) {
  const timestamp = headers['x-webhook-timestamp'];
  const signature = headers['x-webhook-signature'];
  if (typeof timestamp !== 'string' || !/^\d+$/.test(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > TOLERANCE_SECONDS) return false;
  if (typeof signature !== 'string' || !/^sha256=[a-f0-9]{64}$/.test(signature)) return false;
  const received = Buffer.from(signature.slice(7), 'hex');
  return SECRETS.some((secret) => {
    const expected = createHmac('sha256', secret).update(timestamp + '.').update(rawBody).digest();
    return timingSafeEqual(expected, received);
  });
}

const server = createServer((req, res) => {
  if (req.method !== 'POST' || req.url !== '/webhooks/tokenlab') {
    res.writeHead(404).end();
    return;
  }
  const chunks = [];
  req.on('data', (chunk) => chunks.push(chunk));
  req.on('end', () => {
    const rawBody = Buffer.concat(chunks); // exakte Bytes verifizieren, vor JSON.parse
    if (!verify(rawBody, req.headers)) {
      res.writeHead(401).end();
      return;
    }
    const event = JSON.parse(rawBody.toString('utf8'));
    if (event.id !== req.headers['x-webhook-id']) {
      res.writeHead(400).end();
      return;
    }
    if (!seen.has(event.id)) {
      seen.add(event.id);
      enqueue(event); // übergeben; langsame Arbeit außerhalb der Anfrage erledigen
    }
    res.writeHead(204).end();
  });
});

function enqueue(event) {
  console.log('queued', event.type, event.data?.taskId);
}

server.listen(Number(process.env.PORT ?? 3000));

Der Empfänger wurde am 28.09.2026 lokal gegen Anfragen getestet, die exakt wie der Produktions-Sender signiert waren: gültige Zustellung, doppelte Zustellung, vorheriges Secret während der Rotation, falsches Secret, veralteter Zeitstempel, Header- und Body-ID-Nichtübereinstimmung, manipulierter Body und neu serialisiertes JSON. Acht Fälle, alle bestanden. Ein Duplikat wurde einmal in die Warteschlange gestellt.

Die Python-Seite ist eine einzelne Verifizierungsfunktion. Sie vergleicht Signaturen mit hmac.compare_digest und erwartet die rohen Body-Bytes von Flasks request.get_data() oder FastAPIs await request.body().

import hashlib
import hmac
import re
import time

TOLERANCE_SECONDS = 300
SIGNATURE_RE = re.compile(r"^sha256=[a-f0-9]{64}$")


def verify_webhook(raw_body: bytes, headers, secrets: list[str]) -> bool:
    """Einen TokenLab Webhook gegen ein oder mehrere whsec_ Secrets prüfen.

    raw_body müssen die exakten Anfrage-Bytes sein (Flask: request.get_data(),
    FastAPI/Starlette: await request.body()), gelesen vor jedem JSON-Parsing.
    """
    timestamp = headers.get("x-webhook-timestamp", "")
    signature = headers.get("x-webhook-signature", "")
    if not timestamp.isdigit() or abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
        return False
    if not SIGNATURE_RE.match(signature):
        return False
    received = signature.removeprefix("sha256=")
    for secret in secrets:
        expected = hmac.new(secret.encode(), timestamp.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
        if hmac.compare_digest(expected, received):
            return True
    return False

Getestet am 28.09.2026: gültig, vorheriges Secret, falsches Secret, veralteter Zeitstempel, manipulierter Body und ein Body, der mit Standard-json.dumps-Abständen neu serialisiert wurde. Sechs Fälle, alle bestanden.

Tun Sie neben der Signatur bei jeder Anfrage drei Dinge:

  • Lehnen Sie Zeitstempel ab, die mehr als 300 Sekunden in der Zukunft liegen. Das sind 5 Minuten, und es begrenzt, wie alt ein Replay sein kann.
  • Bestätigen Sie, dass die Body-id gleich X-Webhook-ID ist.
  • Speichern Sie die Event-ID zusammen mit Ihrem Arbeitselement in einem atomaren Schreibvorgang, abgesichert durch eine eindeutige Constraint. Geben Sie dann schnell 2xx zurück und erledigen Sie die schwere Arbeit aus Ihrer eigenen Warteschlange.

Zustellungen können sich wiederholen und die Reihenfolge ist nicht garantiert. Das Zeitstempel-Fenster begrenzt das Replay-Alter. Event-ID-Deduplizierung verhindert doppelte Verarbeitung.

Wiederholungen, Auto-Pause und das Recovery-Runbook

Jeder Zustellungszyklus unternimmt bis zu drei Versuche.

Versuch Wartezeit davor Versuchs-Timeout
1 keine 10 s
2 1 s 10 s
3 4 s 10 s

Quelle: TokenLab Webhook-Leitfaden, beobachtet am 28.09.2026.

Jeder Versuch erhält einen frischen Zeitstempel und eine neue Signatur. Das bedeutet, dass Ihre Signaturprüfung den Zeitstempel aus derselben Anfrage verwenden muss, nicht einen zwischengespeicherten Wert.

Wiederholbare Antworten: Netzwerkfehler, 429 und 5xx. Nicht wiederholt innerhalb des Zyklus: andere 4xx, Redirects und ungültige Netzwerkziele. Vorübergehende Fehler können spätere Wiederholungen desselben Events mit derselben Zustellungs-ID auslösen, was ein weiterer Grund ist, warum Deduplizierung nicht optional ist.

Zehn aufeinanderfolgende fehlgeschlagene Zyklen pausieren den Endpoint automatisch.

Wenn Ihr Empfänger offline war, arbeiten Sie dies in der folgenden Reihenfolge ab:

  1. Reparieren Sie den Empfänger. Bestätigen Sie, dass er rohe Bytes liest und schnell 2xx zurückgibt.
  2. Setzen Sie den Endpoint mit PATCH {"is_active": true} fort. Dies setzt den Fehlerzähler zurück.
  3. Senden Sie einen Test mit POST …/test. Ein 200 von der Test-API bedeutet nur, dass der Versuch aufgezeichnet wurde. Prüfen Sie die Zustellungshistorie und bestätigen Sie outcome == "delivered".
  4. Gleichen Sie die Lücke ab. Nehmen Sie die Task-IDs, die Sie gespeichert haben, während der Endpoint pausiert war, und rufen Sie für jede GET /v1/tasks/{id} auf.
  5. Erst dann vertrauen Sie dem Webhook-Stream wieder.

Die Zustellungshistorie liefert Ihnen outcome, http_status, attempts und delivered_at. Sie speichert nur Metadaten, keine Payloads. Alte Events können nicht manuell erneut gesendet werden, daher ist Schritt 4 nicht optional. Ihre gespeicherten Task-IDs sind der Wiederherstellungspfad.

Secret rotieren, ohne Events zu verlieren

Die Rotation ist nicht umkehrbar, planen Sie also das Zeitfenster, bevor Sie beginnen.

  1. Rufen Sie POST /v1/management/webhooks/{webhookId}/rotate-secret auf. Die Antwort gibt das neue Secret einmalig zurück.
  2. Fügen Sie das neue Secret zu Ihrer Verifizierungsliste auf dem Empfänger hinzu. Behalten Sie das alte ebenfalls in dieser Liste.
  3. Stellen Sie die Änderung am Empfänger bereit, bevor Sie irgendetwas verwerfen. Die Liste muss beide Secrets gleichzeitig enthalten.
  4. Senden Sie einen Test und bestätigen Sie outcome == "delivered" in der Historie.
  5. Entfernen Sie nach einem kurzen Zeitfenster das alte Secret und stellen Sie erneut bereit.

Unterwegs befindliche Zustellungen können noch die vorherige Signatur tragen. Wenn Sie Secrets in einem Schritt austauschen, verlieren Sie diese Events. Ein Verifizierer, der nur ein Secret hält, kann Zustellungen ablehnen, die kurz vor der Rotation signiert wurden.

Webhooks von MCP verwalten

Wenn Sie TokenLab von einem Agenten aus steuern, macht der MCP-Server denselben Lebenszyklus verfügbar. Verwenden Sie @tokenlabai/mcp-server mit dem full-Profil. Die Tools sind list_webhooks, create_webhook, get_webhook, update_webhook, delete_webhook, rotate_webhook_secret, test_webhook und list_webhook_deliveries.

Der Server liest das Management Token aus TOKENLAB_MANAGEMENT_TOKEN. Das neueste veröffentlichte Paket, beobachtet am 28.09.2026, ist 0.6.24. MCP bearbeitet dieselben Endpoints, die Sie im Dashboard sehen, es gibt also keinen separaten Zustand abzugleichen.

FAQ

Senden Image-Tasks Webhooks?

Ja. Jeder asynchrone Task im Workspace, einschließlich Image-Tasks, sendet sein Endzustands-Event an die Endpoints, die für diesen Event-Typ abonniert sind. Das Feld taskType im Payload teilt Ihnen mit, um welche Art von Task es sich handelte, zum Beispiel video oder image. Synchrone Ergebnisse sind nicht abgedeckt.

Was passiert, wenn mein Endpoint offline ist?

Jeder Zyklus versucht es bis zu dreimal erneut. Zehn aufeinanderfolgende fehlgeschlagene Zyklen pausieren den Endpoint automatisch. Vorübergehende Zustellungsfehler können später mit derselben Zustellungs-ID erneut versucht werden. Sobald der Endpoint pausiert ist, werden Events aus der Pause nicht später zugestellt und können nicht manuell erneut gesendet werden. Reparieren Sie den Empfänger, setzen Sie den Endpoint fort, senden Sie einen Test und gleichen Sie dann die Tasks ab, die Sie während der Lücke erstellt haben, indem Sie GET /v1/tasks/{id} mit Ihren gespeicherten Task-IDs aufrufen.

Kann ich ein altes Event erneut senden?

Nein. Die Zustellungshistorie enthält nur Metadaten, keine Payloads, und es gibt kein manuelles Replay. Das Zeitstempel-Fenster lehnt zudem alles ab, was älter als 300 Sekunden ist. Der Abgleich über die Task-API ist der unterstützte Weg, um aufzuholen.

Ist task.failed mit retryable: true sicher für eine automatische erneute Übermittlung?

Nein. retryable beschreibt den Fehler bei der Generierung. Es ist keine Anweisung zur erneuten Übermittlung. Eine neue Übermittlung ist ein neuer abrechenbarer Task, entscheiden Sie also selbst über die Wiederholung und berücksichtigen Sie die Kosten.

Verwendet die Seedance-Kompatibilitäts-API diese Webhooks?

Nein. Ihr pro-Anfrage callback_url ist ein separater Vertrag mit eigenem Payload. Er verwendet keine Workspace-Events oder diese HMAC-Header, richten Sie also keinen Verifizierer auf beides gleichzeitig.

Beginnen Sie mit dem vollständigen Vertrag im Webhook-Leitfaden, erstellen Sie dann einen API Key und aktivieren Sie Ihren ersten Endpoint in dem Workspace, der Ihre Tasks übermittelt.

Quellen

Kürzlich veröffentlichte Modelle

Mit den Modellen aus diesem Leitfaden bauen

Preise vergleichen, Routen testen und aus der Recherche einen laufenden API-Aufruf machen.