Webhooks & Callbacks

Comment CodeCourier gère les webhooks entrants de Clerk (cycle de vie utilisateur), la vérification de signature Svix, et le point de terminaison de callback interne Trigger.dev avec une référence complète des opérations organisée par domaine.

14 min lire
webhooksclerksvix

Les webhooks permettent à des services externes de notifier CodeCourier d’événements en temps réel. CodeCourier traite les webhooks de Clerk (pour les événements de cycle de vie utilisateur) et expose un point de terminaison de callback interne pour Trigger.dev (pour le reporting de progression de jobs en arrière-plan et les opérations de données). Cette page documente les deux surfaces de webhook, leurs modèles d’authentification, les formats de payload, la vérification de signature, et la référence complète des opérations de callback Trigger.dev organisée par domaine.

Webhooks Clerk

CodeCourier enregistre un point de terminaison de webhook à /clerk/webhook pour recevoir les événements de cycle de vie utilisateur de Clerk. Le cas d’usage principal est la gestion de la suppression d’utilisateur - quand un utilisateur supprime son compte Clerk, CodeCourier reçoit un webhook et peut nettoyer les données associées.

Événements pris en charge

  • user.deleted - Déclenché quand un compte utilisateur est supprimé dans Clerk. CodeCourier utilise ceci pour déclencher des workflows de nettoyage de données, incluant la suppression ou l’anonymisation des ressources appartenant à l’utilisateur.

Format du payload

Les payloads de webhook Clerk suivent le format standard Svix. Chaque payload contient :

  • type - La chaîne de type d’événement (p. ex., "user.deleted").
  • data - Le payload d’événement contenant la ressource affectée. Pour les événements utilisateur, cela inclut id (l’ID utilisateur Clerk), email_addresses, et d’autres champs utilisateur.
  • object - Toujours "event".

Vérification de signature

Tous les webhooks Clerk entrants sont vérifiés en utilisant le protocole de signature Svix. Ceci garantit que les payloads de webhook proviennent réellement de Clerk et n’ont pas été altérés en transit.

Processus de vérification

  1. Extraction des headers. Le serveur lit trois headers requis de la requête entrante : svix-id (identifiant unique de message), svix-timestamp (timestamp Unix en secondes), et svix-signature (une ou plusieurs signatures versionnées).
  2. Validation du timestamp. Le serveur vérifie que le timestamp est dans une fenêtre de cinq minutes par rapport à l’heure actuelle. Les requêtes hors de cette fenêtre de tolérance sont rejetées pour prévenir les attaques par rejeu.
  3. Calcul de la signature attendue. Le contenu signé est construit comme {svix-id}.{svix-timestamp}.{raw-body}. Le serveur calcule un HMAC-SHA256 de ce contenu en utilisant le secret du webhook (la variable d’environnement CLERK_WEBHOOK_SECRET, avec le préfixe whsec_ retiré et le reste décodé en base64).
  4. Comparaison des signatures. Le header svix-signature peut contenir plusieurs signatures séparées par des espaces, chacune préfixée par une version (p. ex., v1,<base64>). Le serveur vérifie chaque signature v1 par rapport à la valeur calculée en utilisant une comparaison caractère par caractère à temps constant pour prévenir les attaques par canal auxiliaire temporel.
  5. Acceptation ou rejet. Si une signature correspond, le webhook est accepté et traité. Sinon, il est rejeté avec un statut 401.

Variables d’environnement

  • CLERK_WEBHOOK_SECRET - Le secret de signature Svix de votre dashboard Clerk. C’est une clé HMAC encodée en base64 préfixée par whsec_. Le serveur refusera de traiter tout webhook si cette variable n’est pas définie.

Point de terminaison de callback Trigger.dev

Le point de terminaison /trigger/callback est une API interne utilisée exclusivement par les jobs en arrière-plan Trigger.dev de CodeCourier. Elle n’est pas destinée à un usage externe direct - elle est documentée ici afin que les développeurs puissent comprendre le flux de données entre la couche d’orchestration et la base de données Convex.

Les tâches Trigger.dev appellent ce point de terminaison pour écrire des données vers Convex (mises à jour de statut de run, messages de sandbox, extraction de learnings, enregistrement d’usage, etc.) sans nécessiter d’accès direct au client Convex depuis le runtime Trigger.dev.

Authentification

Le point de terminaison de callback utilise l’authentification par bearer token. Le token est défini dans la variable d’environnement TRIGGER_CALLBACK_SECRET et doit être inclus en tant que Authorization: Bearer <token> dans chaque requête. Le serveur effectue une comparaison à temps constant par correspondance XOR d’octets.

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

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

Avertissement

Le point de terminaison /trigger/callback utilise un secret distinct de la clé API du projet (clés cc_live_*). Il est protégé par la variable d’environnement TRIGGER_CALLBACK_SECRET et n’est pas accessible avec les clés API utilisateur. Tenter de l’appeler avec une clé cc_live_* entraînera une erreur 401 Unauthorized.

Format de requête

Chaque requête de callback a la même structure d’enveloppe :

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

Format de réponse

Les opérations réussies renvoient un HTTP 200 avec { "result": <value> }. Les opérations échouées renvoient le code de statut HTTP approprié (400, 401, ou 500) avec { "error": "message" }.

Référence des opérations de callback

Les opérations sont organisées par domaine. Chaque entrée montre le nom de l’opération et une description de ce qu’elle fait et des args qu’elle attend.

run.* - Opérations Workflow Run

  • run.get - Récupère un enregistrement de run par ID. Args : { runId }
  • run.create - Crée un nouvel enregistrement de run dans la base de données (appelé au démarrage de l’orchestration). Args : payload complet de création de run incluant workflowId, prompt, status, et champs optionnels.
  • run.updateStatus - Met à jour le statut d’un run existant (p. ex., runningcompleted). Args : { runId, status, completedAt? }
  • run.updatePr - Enregistre l’URL et le statut de PR d’un run. Args : { runId, prUrl, prStatus, prNumber? }
  • run.createChainRun - Crée un run faisant partie d’une sprint chain, en le liant à l’enregistrement de chain. Args : { chainId, sprintIndex, ... }
  • run.updateProgress - Écrit des informations de progression incrémentale sur un run (p. ex., description de l’étape actuelle, compteur d’itérations). Args : { runId, progress }
  • run.setStopFlag - Définit le flag stopAfterCurrentTurn sur un run pour l’arrêter proprement une fois le tour d’agent actuel terminé. Args : { runId, stop: true }

runStep.* - Opérations Run Step

  • runStep.create - Crée un nouvel enregistrement d’étape sous un run. Args : { runId, role, name, stepIndex }. Rôles valides : designer, checker, researcher, evaluator, judge, answerer.
  • runStep.updateStatus - Met à jour le statut d’une étape et écrit optionnellement des scores de qualité ou des résultats de test. Args : { stepId, status, qualityScores?, testResults? }

sandbox.* - Opérations Sandbox

  • sandbox.get - Récupère un enregistrement de sandbox par ID. Args : { sandboxId }
  • sandbox.create - Enregistre une sandbox E2B nouvellement provisionnée dans la base de données. Args : payload complet de création de sandbox incluant runId, e2bSandboxId.
  • sandbox.updateStatus - Met à jour le statut de cycle de vie de la sandbox (p. ex., running killed). Args : { sandboxId, status }
  • sandbox.setTriggerRunId - Associe un ID de run de tâche Trigger.dev à une sandbox pour le traçage. Args : { sandboxId, triggerRunId }
  • sandbox.updateLearningStatus - Met à jour le statut de l’extraction de learning d’une sandbox. Args : { sandboxId, learningStatus }
  • sandbox.updatePr - Écrit les métadonnées de PR (URL, numéro, statut) sur l’enregistrement de sandbox. Args : { sandboxId, prUrl, prNumber, prStatus }
  • sandbox.hasAssistantMessages - Vérifie si une sandbox a des messages assistant stockés (utilisé pour déterminer s’il faut émettre une notification). Args : { sandboxId }. Renvoie { result: boolean }.

message.* - Opérations Sandbox Message

  • message.store - Persiste un seul message terminé dans le journal de messages de la sandbox. Args : { sandboxId, role, content, timestamp }
  • message.streamCreate - Initialise un enregistrement de message en streaming pour une nouvelle réponse assistant. Args : { sandboxId, messageId }
  • message.streamAppend - Ajoute un fragment de texte à un message en streaming en cours. Args : { messageId, chunk }
  • message.streamFinalize - Marque un message en streaming comme terminé et écrit le contenu accumulé final. Args : { messageId, finalContent }
  • message.listBySandbox - Récupère tous les messages d’une sandbox, ordonnés par timestamp. Args : { sandboxId }

issue.* - Opérations Issue

  • issue.getByRun - Récupère l’issue liée à un run (le cas échéant). Utilisé par l’orchestrateur pour injecter le contexte de l’issue dans le prompt de l’agent. Args : { runId }

issueSession.* - Opérations Issue Session

  • issueSession.get - Récupère un enregistrement de session d’issue. Args : { sessionId }
  • issueSession.createSandbox - Crée et lie une sandbox à une session d’issue. Args : { sessionId, sandboxPayload }
  • issueSession.updateStatus - Met à jour le statut de cycle de vie d’une session d’issue. Args : { sessionId, status }
  • issueSession.createIssuesFromJson - Crée en masse des issues à partir d’un tableau JSON découvert par l’agent de scan. Args : { sessionId, issues: Issue[] }
  • issueSession.createSessionQuestionsFromJson - Crée en masse des questions de session pour une answering session à partir d’un tableau JSON produit par l’agent de génération de questions. Args : { answeringSessionId, questions: Question[] }
  • issueSession.updateIteration - Incrémente le compteur d’itérations sur une session d’issue (utilisé pour le scan multi-tour). Args : { sessionId }
  • issueSession.updateProgress - Écrit du texte de progression incrémentale sur une session d’issue. Args : { sessionId, progress }

issueSessionStep.* - Opérations Issue Session Step

  • issueSessionStep.create - Crée un enregistrement d’étape sous une session d’issue. Args : { sessionId, role, name, stepIndex }
  • issueSessionStep.updateStatus - Met à jour le statut d’une étape de session. Args : { stepId, status }

answeringSession.* - Opérations Answering Session

  • answeringSession.get - Récupère une answering session et ses questions. Args : { answeringSessionId }
  • answeringSession.createSandbox - Crée et lie une sandbox à une answering session pour l’agent answerer. Args : { answeringSessionId, sandboxPayload }
  • answeringSession.updateStatus - Met à jour le statut d’une answering session. Args : { answeringSessionId, status }

sessionQuestions.* - Opérations Session Question

  • sessionQuestions.updateAssumptions - Met à jour en masse les hypothèses générées par l’IA pour un ensemble de questions de session (appelé après que l’agent answerer a produit ses réponses initiales). Args : { updates: Array<{ questionId, assumption }> }
  • sessionQuestions.updateAssumptionsByIssueSession - Met à jour les hypothèses pour toutes les questions liées à une session d’issue spécifique (utilisé quand les hypothèses sont dérivées du contexte au niveau de la session plutôt que de questions individuelles). Args : { issueSessionId, assumptions: Record<string, string> }

learning.* - Opérations Learning

  • learning.dispatchExtraction - Planifie un job d’extraction de learning pour une sandbox terminée. Le job d’extraction analyse la conversation de la sandbox et distille des learnings. Args : { sandboxId, runId }
  • learning.store - Persiste un seul enregistrement de learning extrait d’une sandbox. Args : payload de learning avec sandboxId, content, category, et métadonnées optionnelles.
  • learning.bulkStore - Persiste plusieurs enregistrements de learning en une seule opération. Args : { learnings: Learning[] }
  • learning.getCompiled - Récupère le contenu de learning compilé (fusionné et dédupliqué) pour un projet et un rôle. Utilisé par l’orchestrateur pour injecter les learnings accumulés dans les prompts système des agents. Args : { projectId, role }

usage.* - Opérations d’enregistrement d’usage

  • usage.computeCostAndRecord - Calcule le coût d’un run de sandbox terminé (basé sur le nombre de tokens, le temps de compute, et les tarifs de service) et écrit un enregistrement d’usage. Args : { sandboxId, tokenUsage, durationMs, service }

keys.* - Opérations de clé API

  • keys.get - Récupère une clé API de provider spécifique pour un projet (p. ex., clé API Anthropic). Utilisé par les orchestrateurs pour obtenir les clés configurées d’un projet. Args : { projectId, keyType }
  • keys.getWithFallback - Récupère une clé API de provider, en repli sur la valeur par défaut de la plateforme si le projet n’a pas configuré la sienne. Args : { projectId, keyType }

settings.* - Opérations Project Settings

  • settings.get - Récupère les paramètres du projet (surcharges de prompt système, variables d’environnement, configuration git, feature flags). Args : { projectId }

sprintChain.* - Opérations Sprint Chain

  • sprintChain.get - Récupère un enregistrement de sprint chain. Args : { chainId }
  • sprintChain.updateStatus - Met à jour le statut d’une sprint chain (p. ex., running completed). Args : { chainId, status }
  • sprintChain.updatePr - Ajoute une nouvelle URL de PR de sprint au tableau sprintPrUrls de la chain et met à jour l’index de sprint actuel. Args : { chainId, prUrl, sprintIndex }

workflow.* - Opérations Workflow

  • workflow.get - Récupère un enregistrement de blueprint de workflow incluant la configuration des étapes et les assignations de personas. Args : { workflowId }

persona.* - Opérations Persona

  • persona.get - Récupère un enregistrement de persona (sélection de modèle, prompt système, température, et autre configuration d’agent). Args : { personaId }

contexts.* - Opérations Context

  • contexts.getByIdInternal - Récupère un enregistrement de context par ID pour usage au sein du runtime Trigger.dev. Renvoie les métadonnées de context complètes sans authentification par clé API publique. Args : { contextId }

contextVersions.* - Opérations Context Version

  • contextVersions.getActiveInternal - Récupère le contenu de la version active d’un context. C’est l’opération principale utilisée par l’orchestrateur pour injecter le contenu du context dans les prompts d’agent au moment du run. Args : { contextId }. Renvoie { version, content, publishedAt }.
  • contextVersions.ensureActiveInternal - Récupère la version active, en créant une version 1 vierge si aucune n’existe. Utilisé pour le bootstrap de nouveaux contexts. Args : { contextId }

learningVersions.* - Opérations Learning Version

  • learningVersions.getActiveForRole - Récupère la version de learning compilée active pour un rôle d’agent spécifique au sein d’un projet. Les learnings sont compilés par rôle afin que les agents designer reçoivent des learnings spécifiques au design et les agents checker reçoivent des learnings spécifiques à la vérification. Args : { projectId, role }. Renvoie le contenu de learning compilé ou null si aucune version n’a été compilée.

Politiques de nouvelle tentative

Nouvelles tentatives des webhooks Clerk

Clerk (via Svix) retente automatiquement les livraisons de webhook échouées en utilisant un calendrier de backoff exponentiel. Si CodeCourier renvoie une réponse non-2xx, Svix retentera la livraison au cours des heures et jours suivants. La fenêtre de tolérance de cinq minutes sur le timestamp s’applique au timestamp d’origine, pas à l’heure de la nouvelle tentative, donc les webhooks retentés peuvent être rejetés s’ils arrivent trop tard.

Pour garantir un traitement fiable :

  • Renvoyez une réponse 200 aussi rapidement que possible, même si le traitement réel doit se produire de façon asynchrone.
  • Si le traitement échoue après acceptation du webhook, utilisez ctx.scheduler.runAfter de Convex pour réessayer en interne plutôt que de compter sur les nouvelles tentatives de Svix.

Nouvelles tentatives des callbacks Trigger.dev

Les tâches Trigger.dev implémentent leur propre logique de nouvelle tentative. Si un callback vers CodeCourier échoue (erreur réseau ou réponse 5xx), la tâche Trigger.dev capturera l’erreur et pourra retenter l’opération de callback individuelle. L’architecture à double try-catch dans le gestionnaire de callback garantit qu’une réponse JSON est toujours renvoyée, empêchant les réinitialisations TCP qui pourraient causer des nouvelles tentatives infinies.

Événements de notification

CodeCourier génère aussi des événements de notification internes stockés dans la table notifications. Ce ne sont pas des webhooks externes mais ils servent un objectif similaire pour l’UI du dashboard. Les types de notification suivants sont générés :

  • run_completed - Un run de workflow s’est terminé avec succès.
  • run_failed - Un run de workflow a rencontré une erreur.
  • pr_created - Une pull request a été créée depuis une sandbox ou un run.
  • pr_merged - Une pull request a été fusionnée.
  • pr_failed - La création de pull request a échoué.
  • member_joined - Un nouveau membre a accepté une invitation de projet.
  • workflow_completed - Une exécution de workflow s’est terminée.
  • sprint_completed / sprint_failed - Une sprint chain s’est terminée ou a échoué.

Les notifications sont scopées à un projet et un utilisateur, et incluent un flag read et un timestamp dismissedAt optionnel pour suivre quelles notifications l’utilisateur a vues. Voir l’ Operations API pour les points de terminaison permettant de lister, marquer comme lu, et rejeter les notifications.

Configuration des webhooks

Configuration du webhook Clerk

  1. Naviguez vers le dashboard Clerk de votre application.
  2. Allez dans Webhooks dans la barre latérale gauche.
  3. Cliquez sur Add Endpoint.
  4. Entrez l’URL de votre déploiement Convex CodeCourier suivie de /clerk/webhook (p. ex., https://your-deployment.convex.site/clerk/webhook).
  5. Sélectionnez les événements auxquels vous souhaitez vous abonner (au minimum, user.deleted).
  6. Copiez le secret de signature et définissez-le comme variable d’environnement CLERK_WEBHOOK_SECRET dans votre déploiement Convex.

Tester les webhooks

Utilisez la fonctionnalité «Send test event» du dashboard Clerk pour vérifier que votre point de terminaison fonctionne correctement. Vérifiez les logs de fonction Convex pour toute erreur de vérification de signature ou de traitement d’événement.