Webhooks & Callbacks

Wie CodeCourier eingehende Webhooks von Clerk (User-Lifecycle) verarbeitet, Svix-Signaturverifizierung und der interne Trigger.dev-Callback-Endpunkt mit vollständiger, nach Domäne organisierter Operationsreferenz.

14 Min. Lesezeit
webhooksclerksvix

Webhooks ermöglichen es externen Diensten, CodeCourier in Echtzeit über Ereignisse zu benachrichtigen. CodeCourier verarbeitet Webhooks von Clerk (für User-Lifecycle-Events) und stellt einen internen Callback-Endpunkt für Trigger.dev bereit (zur Fortschritts-Berichterstattung von Hintergrundjobs und für Datenoperationen). Diese Seite dokumentiert beide Webhook-Flächen, ihre Authentifizierungsmodelle, Payload-Formate, Signaturverifizierung und die vollständige, nach Domäne organisierte Trigger.dev-Callback-Operationsreferenz.

Clerk-Webhooks

CodeCourier registriert einen Webhook-Endpunkt unter /clerk/webhook, um User-Lifecycle-Events von Clerk zu empfangen. Der primäre Anwendungsfall ist der Umgang mit User-Löschungen - wenn ein Benutzer sein Clerk-Konto löscht, erhält CodeCourier einen Webhook und kann zugehörige Daten bereinigen.

Unterstützte Events

  • user.deleted -- Wird ausgelöst, wenn ein Benutzerkonto in Clerk gelöscht wird. CodeCourier nutzt dies zum Auslösen von Datenbereinigungs-Workflows, einschließlich Entfernen oder Anonymisieren benutzereigener Ressourcen.

Payload-Format

Clerk-Webhook-Payloads folgen dem Svix-Standardformat. Jede Payload enthält:

  • type -- Der Event-Typ-String (z. B. "user.deleted").
  • data -- Die Event-Payload mit der betroffenen Ressource. Bei User-Events enthält dies id (die Clerk-User-ID), email_addresses und andere User-Felder.
  • object -- Immer "event".

Signaturverifizierung

Alle eingehenden Clerk-Webhooks werden mit dem Svix-Signatur-Protokoll verifiziert. Damit wird sichergestellt, dass Webhook-Payloads tatsächlich von Clerk stammen und auf dem Transportweg nicht manipuliert wurden.

Verifizierungsprozess

  1. Header extrahieren. Der Server liest drei erforderliche Header aus dem eingehenden Request: svix-id (eindeutiger Nachrichten-Bezeichner), svix-timestamp (Unix-Zeitstempel in Sekunden) und svix-signature (eine oder mehrere versionierte Signaturen).
  2. Zeitstempel validieren. Der Server prüft, dass der Zeitstempel innerhalb von fünf Minuten der aktuellen Zeit liegt. Anfragen außerhalb dieses Toleranzfensters werden abgelehnt, um Replay-Attacken vorzubeugen.
  3. Erwartete Signatur berechnen. Der zu signierende Inhalt wird konstruiert als {svix-id}.{svix-timestamp}.{raw-body}. Der Server berechnet einen HMAC-SHA256 dieses Inhalts mit dem Webhook-Secret (der Umgebungsvariable CLERK_WEBHOOK_SECRET, wobei das Präfix whsec_ entfernt und der Rest base64-dekodiert wird).
  4. Signaturen vergleichen. Der Header svix-signature kann mehrere durch Leerzeichen getrennte Signaturen enthalten, jeweils mit einer Version präfigiert (z. B. v1,<base64>). Der Server prüft jede v1-Signatur gegen den berechneten Wert mit einem zeitkonstanten Zeichen-für-Zeichen-Vergleich, um Timing-Seitenkanal-Angriffe zu vermeiden.
  5. Akzeptieren oder ablehnen. Wenn irgendeine Signatur übereinstimmt, wird der Webhook akzeptiert und verarbeitet. Andernfalls wird er mit Status 401 abgelehnt.

Umgebungsvariablen

  • CLERK_WEBHOOK_SECRET -- Das Svix-Signing-Secret aus Ihrem Clerk-Dashboard. Dies ist ein base64-kodierter HMAC-Key mit dem Präfix whsec_. Der Server verarbeitet keine Webhooks, wenn diese Variable nicht gesetzt ist.

Trigger.dev-Callback-Endpunkt

Der Endpunkt /trigger/callback ist eine interne API, die ausschließlich von den Trigger.dev-Hintergrundjobs von CodeCourier verwendet wird. Er ist nicht für die direkte externe Nutzung gedacht - er ist hier dokumentiert, damit Entwickler den Datenfluss zwischen der Orchestrierungs-Schicht und der Convex-Datenbank verstehen können.

Trigger.dev-Tasks rufen diesen Endpunkt auf, um Daten zurück nach Convex zu schreiben (Run-Statusaktualisierungen, Sandbox-Nachrichten, Learning-Extraktion, Usage-Erfassung usw.), ohne direkten Convex-Client-Zugriff aus dem Trigger.dev-Runtime heraus zu benötigen.

Authentifizierung

Der Callback-Endpunkt verwendet Bearer-Token-Authentifizierung. Der Token wird in der Umgebungsvariable TRIGGER_CALLBACK_SECRET gesetzt und muss in jeder Anfrage als Authorization: Bearer <token> enthalten sein. Der Server führt einen zeitkonstanten Vergleich per XOR-Byte-Matching durch.

POST /trigger/callback
Content-Type: application/json
Authorization: Bearer {callback_secret}

{
  "operation": "domain.action",
  "args": { ... }
}

Warnung

Der Endpunkt /trigger/callback verwendet ein separates Secret vom Projekt-API-Schlüssel (cc_live_*-Keys). Er ist durch die Umgebungsvariable TRIGGER_CALLBACK_SECRET geschützt und nicht mit User-API-Schlüsseln erreichbar. Versuche, ihn mit einem cc_live_*-Key aufzurufen, führen zu einem 401-Unauthorized-Fehler.

Request-Format

Jeder Callback-Request hat dieselbe Envelope-Struktur:

json
{
  "operation": "<domain>.<action>",
  "args": {
    // operation-specific arguments
  }
}

Antwortformat

Erfolgreiche Operationen liefern HTTP 200 mit { "result": <value> }. Fehlgeschlagene Operationen liefern den passenden HTTP-Statuscode (400, 401 oder 500) mit { "error": "message" }.

Callback-Operationen-Referenz

Operationen sind nach Domäne organisiert. Jeder Eintrag zeigt den Operationsnamen und eine Beschreibung, was er tut und welche args er erwartet.

run.* - Workflow-Run-Operationen

  • run.get -- Einen Run-Datensatz per ID abrufen. Args: { runId }
  • run.create -- Einen neuen Run-Datensatz in der Datenbank anlegen (zum Orchestrierungs-Start aufgerufen). Args: vollständige Run-Erstellungs-Payload inklusive workflowId, prompt, status und optionalen Feldern.
  • run.updateStatus -- Den Status eines bestehenden Runs aktualisieren (z. B. running completed). Args: { runId, status, completedAt? }
  • run.updatePr -- PR-URL und Status für einen Run erfassen. Args: { runId, prUrl, prStatus, prNumber? }
  • run.createChainRun -- Einen Run als Teil einer Sprint-Chain anlegen und ihn mit dem Chain-Datensatz verknüpfen. Args: { chainId, sprintIndex, ... }
  • run.updateProgress -- Inkrementelle Fortschrittsinformationen in einen Run schreiben (z. B. Beschreibung des aktuellen Schritts, Iterations-Anzahl). Args: { runId, progress }
  • run.setStopFlag -- Das Flag stopAfterCurrentTurn auf einem Run setzen, um nach Abschluss des aktuellen Agent-Turns sauber zu stoppen. Args: { runId, stop: true }

runStep.* - Run-Schritt-Operationen

  • runStep.create -- Einen neuen Schritt-Datensatz unter einem Run anlegen. Args: { runId, role, name, stepIndex }. Gültige Rollen:designer, checker, researcher, evaluator, judge, answerer.
  • runStep.updateStatus -- Den Status eines Schritts aktualisieren und optional Quality Scores oder Testergebnisse schreiben. Args: { stepId, status, qualityScores?, testResults? }

sandbox.* - Sandbox-Operationen

  • sandbox.get -- Einen Sandbox-Datensatz per ID abrufen. Args: { sandboxId }
  • sandbox.create -- Eine neu provisionierte E2B-Sandbox in der Datenbank registrieren. Args: vollständige Sandbox-Erstellungs-Payload inklusive runId, e2bSandboxId.
  • sandbox.updateStatus -- Sandbox-Lifecycle-Status aktualisieren (z. B. running killed). Args: { sandboxId, status }
  • sandbox.setTriggerRunId -- Eine Trigger.dev-Task-Run-ID zwecks Tracing mit einer Sandbox verknüpfen. Args: { sandboxId, triggerRunId }
  • sandbox.updateLearningStatus -- Den Status der Learning-Extraktion für eine Sandbox aktualisieren. Args: { sandboxId, learningStatus }
  • sandbox.updatePr -- PR-Metadaten (URL, Nummer, Status) in den Sandbox-Datensatz schreiben. Args: { sandboxId, prUrl, prNumber, prStatus }
  • sandbox.hasAssistantMessages -- Prüfen, ob eine Sandbox gespeicherte Assistant-Nachrichten hat (verwendet, um zu entscheiden, ob eine Benachrichtigung ausgelöst wird). Args: { sandboxId }. Liefert { result: boolean }.

message.* - Sandbox-Nachrichten-Operationen

  • message.store -- Eine einzelne abgeschlossene Nachricht in das Sandbox-Nachrichten-Log persistieren. Args: { sandboxId, role, content, timestamp }
  • message.streamCreate -- Einen Streaming-Nachrichten-Datensatz für eine neue Assistant-Antwort initialisieren. Args: { sandboxId, messageId }
  • message.streamAppend -- Einen Text-Chunk an eine laufende Streaming-Nachricht anfügen. Args: { messageId, chunk }
  • message.streamFinalize -- Eine Streaming-Nachricht als abgeschlossen markieren und den final akkumulierten Inhalt schreiben. Args: { messageId, finalContent }
  • message.listBySandbox -- Alle Nachrichten einer Sandbox abrufen, geordnet nach Zeitstempel. Args: { sandboxId }

issue.* - Issue-Operationen

  • issue.getByRun -- Das mit einem Run verknüpfte Issue abrufen (falls vorhanden). Wird vom Orchestrator verwendet, um Issue-Kontext in den Agent-Prompt einzufügen. Args: { runId }

issueSession.* - Issue-Sitzungs-Operationen

  • issueSession.get -- Einen Issue-Sitzungs-Datensatz abrufen. Args: { sessionId }
  • issueSession.createSandbox -- Eine Sandbox erstellen und mit einer Issue-Sitzung verknüpfen. Args: { sessionId, sandboxPayload }
  • issueSession.updateStatus -- Den Lifecycle-Status einer Issue-Sitzung aktualisieren. Args: { sessionId, status }
  • issueSession.createIssuesFromJson -- Issues massenweise aus einem vom Scan-Agenten entdeckten JSON-Array anlegen. Args: { sessionId, issues: Issue[] }
  • issueSession.createSessionQuestionsFromJson -- Sitzungsfragen für eine Antwortsitzung massenweise aus einem JSON-Array anlegen, das vom Fragenerstellungs-Agenten erzeugt wurde. Args: { answeringSessionId, questions: Question[] }
  • issueSession.updateIteration -- Den Iterations-Zähler einer Issue-Sitzung erhöhen (verwendet für mehrturniges Scanning). Args: { sessionId }
  • issueSession.updateProgress -- Inkrementellen Fortschrittstext in eine Issue-Sitzung schreiben. Args: { sessionId, progress }

issueSessionStep.* - Issue-Sitzungs-Schritt-Operationen

  • issueSessionStep.create -- Einen Schritt-Datensatz unter einer Issue-Sitzung anlegen. Args: { sessionId, role, name, stepIndex }
  • issueSessionStep.updateStatus -- Den Status eines Sitzungs-Schritts aktualisieren. Args: { stepId, status }

answeringSession.* - Antwortsitzungs-Operationen

  • answeringSession.get -- Eine Antwortsitzung und ihre Fragen abrufen. Args: { answeringSessionId }
  • answeringSession.createSandbox -- Eine Sandbox erstellen und mit einer Antwortsitzung für den Answerer-Agenten verknüpfen. Args: { answeringSessionId, sandboxPayload }
  • answeringSession.updateStatus -- Den Status einer Antwortsitzung aktualisieren. Args: { answeringSessionId, status }

sessionQuestions.* - Sitzungsfragen-Operationen

  • sessionQuestions.updateAssumptions -- Die KI-generierten Annahmen für eine Menge von Sitzungsfragen massenweise aktualisieren (aufgerufen, nachdem der Answerer-Agent seine initialen Antworten erzeugt hat). Args: { updates: Array<{ questionId, assumption }> }
  • sessionQuestions.updateAssumptionsByIssueSession -- Annahmen für alle Fragen aktualisieren, die mit einer bestimmten Issue-Sitzung verknüpft sind (verwendet, wenn Annahmen aus Kontext auf Sitzungsebene statt aus einzelnen Fragen abgeleitet werden). Args: { issueSessionId, assumptions: Record<string, string> }

learning.* - Learning-Operationen

  • learning.dispatchExtraction -- Einen Learning-Extraktions-Job für eine abgeschlossene Sandbox planen. Der Extraktions-Job analysiert die Sandbox-Konversation und destilliert Learnings. Args: { sandboxId, runId }
  • learning.store -- Einen einzelnen Learning-Datensatz, extrahiert aus einer Sandbox, persistieren. Args: Learning-Payload mit sandboxId, content, category und optionalen Metadaten.
  • learning.bulkStore -- Mehrere Learning-Datensätze in einer einzigen Operation persistieren. Args: { learnings: Learning[] }
  • learning.getCompiled -- Den kompilierten (zusammengeführten und deduplizierten) Learning-Inhalt für ein Projekt und eine Rolle abrufen. Wird vom Orchestrator verwendet, um akkumulierte Learnings in Agent-System-Prompts einzufügen. Args: { projectId, role }

usage.* - Usage-Erfassungs-Operationen

  • usage.computeCostAndRecord -- Die Kosten für einen abgeschlossenen Sandbox-Run berechnen (basierend auf Token-Anzahlen, Compute-Zeit und Service-Tarifen) und einen Usage-Datensatz schreiben. Args: { sandboxId, tokenUsage, durationMs, service }

keys.* - API-Schlüssel-Operationen

  • keys.get -- Einen bestimmten Provider-API-Schlüssel eines Projekts abrufen (z. B. Anthropic-API-Schlüssel). Wird von Orchestratoren verwendet, um die konfigurierten Schlüssel eines Projekts zu erhalten. Args: { projectId, keyType }
  • keys.getWithFallback -- Einen Provider-API-Schlüssel abrufen und auf den Plattform-Default zurückfallen, falls das Projekt keinen eigenen konfiguriert hat. Args: { projectId, keyType }

settings.* - Projekteinstellungs-Operationen

  • settings.get -- Projekteinstellungen abrufen (System-Prompt-Overrides, Umgebungsvariablen, Git-Konfiguration, Feature-Flags). Args: { projectId }

sprintChain.* - Sprint-Chain-Operationen

  • sprintChain.get -- Einen Sprint-Chain-Datensatz abrufen. Args: { chainId }
  • sprintChain.updateStatus -- Den Status einer Sprint-Chain aktualisieren (z. B. running completed). Args: { chainId, status }
  • sprintChain.updatePr -- Eine neue Sprint-PR-URL an das sprintPrUrls-Array der Chain anhängen und den aktuellen Sprint-Index aktualisieren. Args: { chainId, prUrl, sprintIndex }

workflow.* - Workflow-Operationen

  • workflow.get -- Einen Workflow-Blueprint-Datensatz inklusive Schrittkonfiguration und Persona-Zuweisungen abrufen. Args: { workflowId }

persona.* - Persona-Operationen

  • persona.get -- Einen Persona-Datensatz abrufen (Modellauswahl, System-Prompt, Temperatur und andere Agent-Konfiguration). Args: { personaId }

contexts.* - Kontext-Operationen

  • contexts.getByIdInternal -- Einen Kontext-Datensatz per ID zur Verwendung innerhalb des Trigger.dev-Runtime abrufen. Liefert vollständige Kontext-Metadaten ohne öffentliche API-Schlüssel-Authentifizierung. Args: { contextId }

contextVersions.* - Kontext-Versions-Operationen

  • contextVersions.getActiveInternal -- Den Inhalt der aktiven Version eines Kontextes abrufen. Dies ist die primäre Operation, die der Orchestrator nutzt, um Kontextinhalt zur Laufzeit in Agent-Prompts einzufügen. Args: { contextId }. Liefert { version, content, publishedAt }.
  • contextVersions.ensureActiveInternal -- Die aktive Version abrufen und eine leere Version 1 erzeugen, falls keine existiert. Wird für das Bootstrapping neuer Kontexte verwendet. Args: { contextId }

learningVersions.* - Learning-Versions-Operationen

  • learningVersions.getActiveForRole -- Die aktive kompilierte Learning-Version für eine bestimmte Agent-Rolle innerhalb eines Projekts abrufen. Learnings werden pro Rolle kompiliert, sodass Designer-Agenten designer-spezifische Learnings und Checker-Agenten checker-spezifische Learnings erhalten. Args: { projectId, role }. Liefert den kompilierten Learning-Inhalt oder null, wenn keine Version kompiliert wurde.

Retry-Richtlinien

Clerk-Webhook-Retries

Clerk (über Svix) wiederholt fehlgeschlagene Webhook-Lieferungen automatisch mit einem Exponential-Backoff-Zeitplan. Wenn CodeCourier eine Nicht-2xx-Antwort liefert, wiederholt Svix die Lieferung über die folgenden Stunden und Tage. Das Fünf-Minuten-Toleranzfenster bezieht sich auf den ursprünglichen Zeitstempel, nicht auf die Retry-Zeit, sodass erneut versuchte Webhooks abgelehnt werden können, wenn sie zu spät eintreffen.

Für zuverlässige Verarbeitung:

  • Liefern Sie so schnell wie möglich eine 200-Antwort, selbst wenn die eigentliche Verarbeitung asynchron erfolgen muss.
  • Wenn die Verarbeitung nach Annahme des Webhooks fehlschlägt, verwenden Sie Convex' ctx.scheduler.runAfter, um intern zu wiederholen, statt sich auf Svix-Retries zu verlassen.

Trigger.dev-Callback-Retries

Trigger.dev-Tasks implementieren ihre eigene Retry-Logik. Wenn ein Callback an CodeCourier fehlschlägt (Netzwerkfehler oder 5xx-Antwort), fängt der Trigger.dev-Task den Fehler ab und kann die einzelne Callback-Operation erneut versuchen. Die Doppelt-try-catch-Architektur im Callback-Handler stellt sicher, dass immer eine JSON-Antwort zurückgegeben wird, was TCP-Resets verhindert, die endlose Retries verursachen könnten.

Benachrichtigungs-Events

CodeCourier erzeugt außerdem interne Benachrichtigungs-Events, die in der Tabelle notifications gespeichert werden. Dies sind keine externen Webhooks, dienen aber einem ähnlichen Zweck für die Dashboard-UI. Die folgenden Benachrichtigungstypen werden erzeugt:

  • run_completed -- Ein Workflow-Run wurde erfolgreich abgeschlossen.
  • run_failed -- Ein Workflow-Run hat einen Fehler gemeldet.
  • pr_created -- Ein Pull Request wurde aus einer Sandbox oder einem Run erstellt.
  • pr_merged -- Ein Pull Request wurde gemerged.
  • pr_failed -- Die Erstellung eines Pull Requests ist fehlgeschlagen.
  • member_joined -- Ein neues Mitglied hat eine Projekt-Einladung angenommen.
  • workflow_completed -- Eine Workflow-Ausführung wurde abgeschlossen.
  • sprint_completed / sprint_failed -- Eine Sprint-Chain wurde abgeschlossen oder ist fehlgeschlagen.

Benachrichtigungen sind auf ein Projekt und einen Benutzer beschränkt und enthalten ein read-Flag und einen optionalen dismissedAt-Zeitstempel zur Verfolgung, welche Benachrichtigungen der Benutzer gesehen hat. Siehe die Operations-API für Endpunkte zum Auflisten, Als-gelesen-Markieren und Verwerfen von Benachrichtigungen.

Webhooks einrichten

Clerk-Webhook-Konfiguration

  1. Navigieren Sie zum Clerk-Dashboard Ihrer Anwendung.
  2. Gehen Sie in der linken Seitenleiste zu Webhooks.
  3. Klicken Sie auf Add Endpoint.
  4. Geben Sie die URL Ihres CodeCourier-Convex-Deployments gefolgt von /clerk/webhook ein (z. B. https://your-deployment.convex.site/clerk/webhook).
  5. Wählen Sie die Events aus, die Sie abonnieren möchten (mindestens user.deleted).
  6. Kopieren Sie das Signing-Secret und setzen Sie es als Umgebungsvariable CLERK_WEBHOOK_SECRET in Ihrem Convex-Deployment.

Webhooks testen

Verwenden Sie die Funktion „Send test event“ im Clerk-Dashboard, um zu überprüfen, ob Ihr Endpunkt korrekt funktioniert. Prüfen Sie die Convex-Funktions-Logs auf Fehler bei der Signaturverifizierung oder Event-Verarbeitung.