Webhooks & Callbacks

Come CodeCourier gestisce i webhook in entrata da Clerk (ciclo di vita utente), la verifica della firma Svix e l’endpoint di callback interno Trigger.dev con un riferimento completo delle operazioni organizzato per dominio.

14 min letto
webhooksclerksvix

I webhook permettono a servizi esterni di notificare CodeCourier di eventi in tempo reale. CodeCourier elabora i webhook da Clerk (per gli eventi del ciclo di vita utente) ed espone un endpoint di callback interno per Trigger.dev (per il reporting di avanzamento dei job in background e le operazioni sui dati). Questa pagina documenta entrambe le superfici webhook, i loro modelli di autenticazione, i formati del payload, la verifica della firma e il riferimento completo delle operazioni di callback Trigger.dev organizzato per dominio.

Webhook Clerk

CodeCourier registra un endpoint webhook su /clerk/webhook per ricevere gli eventi del ciclo di vita utente da Clerk. Il caso d’uso principale è la gestione dell’eliminazione utente - quando un utente elimina il proprio account Clerk, CodeCourier riceve un webhook e può ripulire i dati associati.

Eventi supportati

  • user.deleted - Attivato quando un account utente viene eliminato in Clerk. CodeCourier lo usa per attivare workflow di pulizia dati, inclusa la rimozione o l’anonimizzazione delle risorse di proprietà dell’utente.

Formato del payload

I payload dei webhook Clerk seguono il formato standard Svix. Ogni payload contiene:

  • type - La stringa del tipo di evento (ad es., "user.deleted").
  • data - Il payload dell’evento contenente la risorsa interessata. Per gli eventi utente, include id (l’ID utente Clerk), email_addresses, e altri campi utente.
  • object - Sempre "event".

Verifica della firma

Tutti i webhook Clerk in entrata vengono verificati usando il protocollo di firma Svix. Questo garantisce che i payload dei webhook provengano genuinamente da Clerk e non siano stati manomessi durante il transito.

Processo di verifica

  1. Estrazione degli header. Il server legge tre header obbligatori dalla richiesta in entrata: svix-id (identificatore univoco del messaggio), svix-timestamp (timestamp Unix in secondi), e svix-signature (una o più firme con versione).
  2. Validazione del timestamp. Il server verifica che il timestamp sia entro cinque minuti dall’ora corrente. Le richieste al di fuori di questa finestra di tolleranza vengono respinte per prevenire attacchi di replay.
  3. Calcolo della firma attesa. Il contenuto firmato viene costruito come {svix-id}.{svix-timestamp}.{raw-body}. Il server calcola un HMAC-SHA256 di questo contenuto usando il segreto del webhook (la variabile d’ambiente CLERK_WEBHOOK_SECRET, con il prefisso whsec_ rimosso e il resto decodificato in base64).
  4. Confronto delle firme. L’header svix-signature può contenere più firme separate da spazi, ciascuna preceduta da una versione (ad es., v1,<base64>). Il server verifica ogni firma v1 rispetto al valore calcolato usando un confronto carattere per carattere a tempo costante per prevenire attacchi di canale laterale temporale.
  5. Accettazione o rifiuto. Se una firma corrisponde, il webhook viene accettato ed elaborato. Altrimenti, viene respinto con uno stato 401.

Variabili d’ambiente

  • CLERK_WEBHOOK_SECRET - Il segreto di firma Svix dal tuo dashboard Clerk. È una chiave HMAC codificata in base64 con prefisso whsec_. Il server rifiuterà di elaborare qualsiasi webhook se questa variabile non è impostata.

Endpoint di callback Trigger.dev

L’endpoint /trigger/callback è un’API interna usata esclusivamente dai job in background Trigger.dev di CodeCourier. Non è destinata all’uso esterno diretto - è documentata qui affinché gli sviluppatori possano comprendere il flusso di dati tra il livello di orchestrazione e il database Convex.

I task Trigger.dev chiamano questo endpoint per scrivere dati su Convex (aggiornamenti dello stato del run, messaggi sandbox, estrazione di learning, registrazione dell’utilizzo, ecc.) senza richiedere accesso diretto al client Convex dall’interno del runtime Trigger.dev.

Autenticazione

L’endpoint di callback usa l’autenticazione tramite bearer token. Il token è impostato nella variabile d’ambiente TRIGGER_CALLBACK_SECRET e deve essere incluso come Authorization: Bearer <token> in ogni richiesta. Il server esegue un confronto a tempo costante tramite matching XOR dei byte.

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

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

Avvertimento

L’endpoint /trigger/callback usa un segreto separato dalla chiave API del progetto (chiavi cc_live_*). È protetto dalla variabile d’ambiente TRIGGER_CALLBACK_SECRET e non è accessibile con le chiavi API utente. Tentare di chiamarlo con una chiave cc_live_* risulterà in un errore 401 Unauthorized.

Formato della richiesta

Ogni richiesta di callback ha la stessa struttura envelope:

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

Formato della risposta

Le operazioni riuscite restituiscono HTTP 200 con { "result": <value> }. Le operazioni fallite restituiscono il codice di stato HTTP appropriato (400, 401, o 500) con { "error": "message" }.

Riferimento delle operazioni di callback

Le operazioni sono organizzate per dominio. Ogni voce mostra il nome dell’operazione e una descrizione di cosa fa e quali args si aspetta.

run.* - Operazioni Workflow Run

  • run.get - Recupera un record run per ID. Args: { runId }
  • run.create - Crea un nuovo record run nel database (chiamato all’avvio dell’orchestrazione). Args: payload completo di creazione run inclusi workflowId, prompt, status, e campi opzionali.
  • run.updateStatus - Aggiorna lo stato di un run esistente (ad es., runningcompleted). Args: { runId, status, completedAt? }
  • run.updatePr - Registra l’URL e lo stato della PR per un run. Args: { runId, prUrl, prStatus, prNumber? }
  • run.createChainRun - Crea un run come parte di una sprint chain, collegandolo al record della chain. Args: { chainId, sprintIndex, ... }
  • run.updateProgress - Scrive informazioni di avanzamento incrementale su un run (ad es., descrizione dello step corrente, conteggio iterazioni). Args: { runId, progress }
  • run.setStopFlag - Imposta il flag stopAfterCurrentTurn su un run per fermarlo in modo controllato al termine del turno agente corrente. Args: { runId, stop: true }

runStep.* - Operazioni Run Step

  • runStep.create - Crea un nuovo record step sotto un run. Args: { runId, role, name, stepIndex }. Ruoli validi: designer, checker, researcher, evaluator, judge, answerer.
  • runStep.updateStatus - Aggiorna lo stato di uno step e opzionalmente scrive punteggi di qualità o risultati dei test. Args: { stepId, status, qualityScores?, testResults? }

sandbox.* - Operazioni Sandbox

  • sandbox.get - Recupera un record sandbox per ID. Args: { sandboxId }
  • sandbox.create - Registra una sandbox E2B appena provisionata nel database. Args: payload completo di creazione sandbox inclusi runId, e2bSandboxId.
  • sandbox.updateStatus - Aggiorna lo stato del ciclo di vita della sandbox (ad es., running killed). Args: { sandboxId, status }
  • sandbox.setTriggerRunId - Associa un ID di run di task Trigger.dev a una sandbox per il tracing. Args: { sandboxId, triggerRunId }
  • sandbox.updateLearningStatus - Aggiorna lo stato dell’estrazione di learning per una sandbox. Args: { sandboxId, learningStatus }
  • sandbox.updatePr - Scrive i metadati della PR (URL, numero, stato) sul record sandbox. Args: { sandboxId, prUrl, prNumber, prStatus }
  • sandbox.hasAssistantMessages - Verifica se una sandbox ha messaggi assistant memorizzati (usato per determinare se emettere una notifica). Args: { sandboxId }. Restituisce { result: boolean }.

message.* - Operazioni Sandbox Message

  • message.store - Persiste un singolo messaggio completato nel log messaggi della sandbox. Args: { sandboxId, role, content, timestamp }
  • message.streamCreate - Inizializza un record di messaggio in streaming per una nuova risposta assistant. Args: { sandboxId, messageId }
  • message.streamAppend - Aggiunge un frammento di testo a un messaggio in streaming in corso. Args: { messageId, chunk }
  • message.streamFinalize - Segna un messaggio in streaming come completo e scrive il contenuto accumulato finale. Args: { messageId, finalContent }
  • message.listBySandbox - Recupera tutti i messaggi di una sandbox, ordinati per timestamp. Args: { sandboxId }

issue.* - Operazioni Issue

  • issue.getByRun - Recupera l’issue collegata a un run (se presente). Usato dall’orchestratore per iniettare il contesto dell’issue nel prompt dell’agente. Args: { runId }

issueSession.* - Operazioni Issue Session

  • issueSession.get - Recupera un record di issue session. Args: { sessionId }
  • issueSession.createSandbox - Crea e collega una sandbox a una issue session. Args: { sessionId, sandboxPayload }
  • issueSession.updateStatus - Aggiorna lo stato del ciclo di vita di una issue session. Args: { sessionId, status }
  • issueSession.createIssuesFromJson - Crea in blocco issue da un array JSON scoperto dall’agente di scansione. Args: { sessionId, issues: Issue[] }
  • issueSession.createSessionQuestionsFromJson - Crea in blocco domande di session per una answering session da un array JSON prodotto dall’agente di generazione domande. Args: { answeringSessionId, questions: Question[] }
  • issueSession.updateIteration - Incrementa il contatore di iterazioni su una issue session (usato per la scansione multi-turno). Args: { sessionId }
  • issueSession.updateProgress - Scrive testo di avanzamento incrementale su una issue session. Args: { sessionId, progress }

issueSessionStep.* - Operazioni Issue Session Step

  • issueSessionStep.create - Crea un record step sotto una issue session. Args: { sessionId, role, name, stepIndex }
  • issueSessionStep.updateStatus - Aggiorna lo stato di uno step di session. Args: { stepId, status }

answeringSession.* - Operazioni Answering Session

  • answeringSession.get - Recupera una answering session e le sue domande. Args: { answeringSessionId }
  • answeringSession.createSandbox - Crea e collega una sandbox a una answering session per l’agente answerer. Args: { answeringSessionId, sandboxPayload }
  • answeringSession.updateStatus - Aggiorna lo stato di una answering session. Args: { answeringSessionId, status }

sessionQuestions.* - Operazioni Session Question

  • sessionQuestions.updateAssumptions - Aggiorna in blocco le assunzioni generate dall’IA per un insieme di domande di session (chiamato dopo che l’agente answerer produce le sue risposte iniziali). Args: { updates: Array<{ questionId, assumption }> }
  • sessionQuestions.updateAssumptionsByIssueSession - Aggiorna le assunzioni per tutte le domande collegate a una specifica issue session (usato quando le assunzioni sono derivate dal contesto a livello di session anziché da domande individuali). Args: { issueSessionId, assumptions: Record<string, string> }

learning.* - Operazioni Learning

  • learning.dispatchExtraction - Pianifica un job di estrazione learning per una sandbox completata. Il job di estrazione analizza la conversazione della sandbox e distilla learning. Args: { sandboxId, runId }
  • learning.store - Persiste un singolo record learning estratto da una sandbox. Args: payload di learning con sandboxId, content, category, e metadati opzionali.
  • learning.bulkStore - Persiste più record learning in un’unica operazione. Args: { learnings: Learning[] }
  • learning.getCompiled - Recupera il contenuto di learning compilato (unito e deduplicato) per un progetto e un ruolo. Usato dall’orchestratore per iniettare i learning accumulati nei prompt di sistema degli agenti. Args: { projectId, role }

usage.* - Operazioni di registrazione utilizzo

  • usage.computeCostAndRecord - Calcola il costo per un run sandbox completato (basato su conteggio token, tempo di compute e tariffe del servizio) e scrive un record di utilizzo. Args: { sandboxId, tokenUsage, durationMs, service }

keys.* - Operazioni chiave API

  • keys.get - Recupera una specifica chiave API del provider per un progetto (ad es., chiave API Anthropic). Usato dagli orchestratori per ottenere le chiavi configurate di un progetto. Args: { projectId, keyType }
  • keys.getWithFallback - Recupera una chiave API del provider, ripiegando sul valore predefinito della piattaforma se il progetto non ha configurato la propria. Args: { projectId, keyType }

settings.* - Operazioni Project Settings

  • settings.get - Recupera le impostazioni del progetto (override del prompt di sistema, variabili d’ambiente, configurazione git, feature flag). Args: { projectId }

sprintChain.* - Operazioni Sprint Chain

  • sprintChain.get - Recupera un record di sprint chain. Args: { chainId }
  • sprintChain.updateStatus - Aggiorna lo stato di una sprint chain (ad es., running completed). Args: { chainId, status }
  • sprintChain.updatePr - Aggiunge un nuovo URL di PR di sprint all’array sprintPrUrls della chain e aggiorna l’indice di sprint corrente. Args: { chainId, prUrl, sprintIndex }

workflow.* - Operazioni Workflow

  • workflow.get - Recupera un record blueprint di workflow inclusa la configurazione degli step e le assegnazioni di persona. Args: { workflowId }

persona.* - Operazioni Persona

  • persona.get - Recupera un record persona (selezione del modello, prompt di sistema, temperatura, e altra configurazione dell’agente). Args: { personaId }

contexts.* - Operazioni Context

  • contexts.getByIdInternal - Recupera un record context per ID per l’uso all’interno del runtime Trigger.dev. Restituisce metadati context completi senza autenticazione con chiave API pubblica. Args: { contextId }

contextVersions.* - Operazioni Context Version

  • contextVersions.getActiveInternal - Recupera il contenuto della versione attiva per un context. Questa è l’operazione principale usata dall’orchestratore per iniettare il contenuto del context nei prompt agente a runtime. Args: { contextId }. Restituisce { version, content, publishedAt }.
  • contextVersions.ensureActiveInternal - Recupera la versione attiva, creando una versione 1 vuota se non ne esiste nessuna. Usato per il bootstrap di nuovi context. Args: { contextId }

learningVersions.* - Operazioni Learning Version

  • learningVersions.getActiveForRole - Recupera la versione di learning compilata attiva per uno specifico ruolo agente all’interno di un progetto. I learning vengono compilati per ruolo così che gli agenti designer ricevano learning specifici per il design e gli agenti checker ricevano learning specifici per la verifica. Args: { projectId, role }. Restituisce il contenuto di learning compilato o null se nessuna versione è stata compilata.

Politiche di retry

Retry dei webhook Clerk

Clerk (tramite Svix) riprova automaticamente le consegne di webhook fallite usando una pianificazione con backoff esponenziale. Se CodeCourier restituisce una risposta non-2xx, Svix riproverà la consegna nelle ore e nei giorni successivi. La finestra di tolleranza di cinque minuti sul timestamp si applica al timestamp originale, non all’ora del retry, quindi i webhook ritentati potrebbero essere respinti se arrivano troppo tardi.

Per garantire un’elaborazione affidabile:

  • Restituisci una risposta 200 il più rapidamente possibile, anche se l’elaborazione effettiva deve avvenire in modo asincrono.
  • Se l’elaborazione fallisce dopo l’accettazione del webhook, usa ctx.scheduler.runAfter di Convex per riprovare internamente anziché affidarti ai retry di Svix.

Retry dei callback Trigger.dev

I task Trigger.dev implementano la propria logica di retry. Se un callback verso CodeCourier fallisce (errore di rete o risposta 5xx), il task Trigger.dev intercetterà l’errore e potrà riprovare la singola operazione di callback. L’architettura a doppio try-catch nel gestore di callback garantisce che venga sempre restituita una risposta JSON, prevenendo reset TCP che potrebbero causare retry infiniti.

Eventi di notifica

CodeCourier genera anche eventi di notifica interni memorizzati nella tabella notifications. Non sono webhook esterni ma servono a uno scopo simile per l’UI della dashboard. Vengono generati i seguenti tipi di notifica:

  • run_completed - Un run di workflow è terminato con successo.
  • run_failed - Un run di workflow ha riscontrato un errore.
  • pr_created - È stata creata una pull request da una sandbox o un run.
  • pr_merged - Una pull request è stata unita.
  • pr_failed - La creazione della pull request è fallita.
  • member_joined - Un nuovo membro ha accettato un invito al progetto.
  • workflow_completed - Un’esecuzione di workflow è terminata.
  • sprint_completed / sprint_failed - Una sprint chain è stata completata o è fallita.

Le notifiche sono scoped a un progetto e un utente, e includono un flag read e un timestamp dismissedAt opzionale per tracciare quali notifiche l’utente ha visto. Consulta l’ Operations API per gli endpoint per elencare, segnare come lette ed eliminare le notifiche.

Configurazione dei webhook

Configurazione del webhook Clerk

  1. Vai al dashboard Clerk della tua applicazione.
  2. Vai su Webhooks nella barra laterale sinistra.
  3. Clicca su Add Endpoint.
  4. Inserisci l’URL del tuo deployment Convex CodeCourier seguito da /clerk/webhook (ad es., https://your-deployment.convex.site/clerk/webhook).
  5. Seleziona gli eventi a cui vuoi iscriverti (come minimo, user.deleted).
  6. Copia il segreto di firma e impostalo come variabile d’ambiente CLERK_WEBHOOK_SECRET nel tuo deployment Convex.

Testare i webhook

Usa la funzione «Send test event» del dashboard Clerk per verificare che il tuo endpoint funzioni correttamente. Controlla i log delle funzioni Convex per eventuali errori nella verifica della firma o nell’elaborazione degli eventi.