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.
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, includeid(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
- 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), esvix-signature(una o più firme con versione). - 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.
- 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’ambienteCLERK_WEBHOOK_SECRET, con il prefissowhsec_rimosso e il resto decodificato in base64). - Confronto delle firme. L’header
svix-signaturepuò contenere più firme separate da spazi, ciascuna preceduta da una versione (ad es.,v1,<base64>). Il server verifica ogni firmav1rispetto al valore calcolato usando un confronto carattere per carattere a tempo costante per prevenire attacchi di canale laterale temporale. - 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 prefissowhsec_. 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
/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:
{
"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 inclusiworkflowId,prompt,status, e campi opzionali.run.updateStatus- Aggiorna lo stato di un run esistente (ad es.,running→completed). 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 flagstopAfterCurrentTurnsu 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 inclusirunId,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 consandboxId,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’arraysprintPrUrlsdella 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 onullse 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.runAfterdi 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
- Vai al dashboard Clerk della tua applicazione.
- Vai su Webhooks nella barra laterale sinistra.
- Clicca su Add Endpoint.
- Inserisci l’URL del tuo deployment Convex CodeCourier seguito da
/clerk/webhook(ad es.,https://your-deployment.convex.site/clerk/webhook). - Seleziona gli eventi a cui vuoi iscriverti (come minimo,
user.deleted). - Copia il segreto di firma e impostalo come variabile d’ambiente
CLERK_WEBHOOK_SECRETnel 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.