Para Builders

Webhooks

Um webhook e a porta pela qual seu codigo entra no Daguito. Qualquer sistema externo —seu backend, um CRM, um cron— pode disparar um fluxo com uma unica chamada. Ha dois transportes: HTTP para chamadas pontuais e WebSocket para fluxos longos em streaming. Cada token e guardado em hash, cada entrega de saida vai assinada e tudo fica registrado.

O que e um webhook

Um webhook e um ponto de entrada com nome que aponta para um fluxo. Voce entrega um token a um sistema externo e esse sistema dispara o fluxo quando precisa. Voce nao expoe sua API: expoe um endpoint estreito e assinado que so sabe executar aquele fluxo.

Voce escolhe o transporte ao criar o webhook, conforme for consumi-lo:

Info

Os tokens nunca sao guardados em texto plano. Um hash SHA-256 e armazenado para resolve-los em O(1), e o texto plano so e devolvido uma vez, ao criar o webhook.

Tipos e transporte

O campo type do webhook decide qual endpoint o token ativa. Ha dois valores: http e streaming.

Webhooks HTTP

Um webhook HTTP expoe dois modos, conforme quanto quem chama pode esperar:

Webhooks de streaming

Um webhook de streaming abre um WebSocket bidirecional em /v1/webhooks/:id/stream. O token viaja como parametro ?token= na conexao. O cliente conduz uma sessao com frames e recebe os eventos do fluxo ao vivo.

Estas sao as tres formas de invocar um webhook, conforme o tipo:

# HTTP — sincronico (one-shot, el token es la unica auth)
POST https://api.daguito.com/h/dgsk_wh_...

# HTTP — asincronico (202 inmediato, auth Bearer)
POST https://api.daguito.com/v1/webhooks/:id/messages
Authorization: Bearer dgsk_wh_...

# Streaming — WebSocket bidireccional
wss://api.daguito.com/v1/webhooks/:id/stream?token=dgsk_wh_...
Atenção

O tipo e imutavel: e fixado na criacao e nao muda depois. Para passar de HTTP a streaming e preciso criar um webhook novo.

Tokens e seguranca

Cada webhook tem um token com o qual se autentica. O modelo e simples e rigoroso:

// Formato y hashing del token
const plaintext = 'dgsk_wh_' + crypto.randomBytes(24).toString('base64url')
const tokenHash = sha256(plaintext)            // solo se guarda el hash
const tokenPreview = 'dgsk_wh_...' + plaintext.slice(-4)  // y una vista previa
Atenção

Nao ha rotacao automatica de tokens nem endpoint de exclusao. Para invalidar um token, mude o status do webhook para revoked; para um novo, crie outro webhook.

HTTP sincrono: POST /h/:token

E a forma mais direta: uma unica chamada que bloqueia ate o fluxo terminar. Ideal para integracoes que precisam da resposta de imediato.

No SDK, use runWebhook para esta chamada sincrona:

import { runWebhook } from '@daguito/sdk'

const result = await runWebhook({
  apiUrl: 'https://api.daguito.com',
  token: 'dgsk_wh_...', // token en texto plano
  input: { user_id: '123', action: 'process' },
  timeoutMs: 30000,
})

console.log(result)
// { ok: boolean, executionId: string, status: string, output: unknown }

HTTP assincrono: POST /v1/webhooks/:id/messages

Quando voce nao quer bloquear, este modo enfileira a mensagem e responde na hora. O fluxo roda a parte e os resultados chegam por callbacks.

Atenção

Esta rota não foi pensada para fluxos muito longos: processa a entrega imediatamente. Para processos de longa duração, é melhor dividi-los em passos mais curtos.

Callbacks de saida

Um callback e um POST de servidor para servidor que o Daguito envia a uma URL sua quando ocorre um evento do fluxo. Assim voce recebe resultados sem precisar fazer polling.

Forma do payload

// Headers del POST saliente
x-daguito-signature: sha256=<hex>    // HMAC-SHA256 del cuerpo, con secret_hash
x-daguito-event:      flow.completed // el tipo de evento
x-daguito-webhook-id: wh_abc123      // id del webhook
Content-Type:         application/json

// Cuerpo
{
  "event": "flow.completed",
  "webhook_id": "wh_abc123",
  "session_key": "webhook:...",
  "flow_id": "...",
  "at": "2026-01-01T12:00:00Z",
  "data": { /* especifico del evento */ }
}

Verificar a assinatura

Recalcule o HMAC sobre o corpo cru (sem reserializar) com o seu segredo e compare com o header. Se nao bater, rejeite a requisicao.

import crypto from 'node:crypto'

const expected = crypto
  .createHmac('sha256', webhookSecret)
  .update(rawBody) // el cuerpo en bytes, sin re-serializar
  .digest('hex')

const received = req.headers['x-daguito-signature'] // sha256=<hex>
if (received !== 'sha256=' + expected) {
  throw new Error('signature mismatch')
}
Dica

Cada tentativa de callback, com sucesso ou falha, fica registrada para que você possa depurar suas entregas.

Streaming por WebSocket

Para fluxos longos —geracoes de LLM, pipelines de visao, varias ferramentas encadeadas— o WebSocket evita o limite do proxy e entrega os eventos a medida que ocorrem.

Ciclo de vida da sessao

O cliente conduz uma sessao com frames: session.start cria o session_key, session.open dispara o fluxo com o base_input, message envia texto ou audio e session.end a encerra. O servidor confirma cada passo e empurra os FlowEvent.

// El cliente envia:
{ "type": "session.start", "session_key": "..." }
{ "type": "session.open", "base_input": { /* ... */ } }
{ "type": "message", "message": { /* normalized message */ } }
{ "type": "tool_result", "call_id": "...", "ok": true, "result": {} }
{ "type": "session.end" }
{ "type": "ping" }

// El servidor responde:
{ "type": "ready", "webhook_id": "wh_abc123" }
{ "type": "session.started", "session_key": "..." }
{ "type": "session.opened", "session_key": "...", "execution_id": "..." }
{ "type": "session.ended" }
{ "type": "pong", "at": "..." }
{ "type": "error", "message": "..." }
// ...y FlowEvent por cada evento que pase el filtro events_downstream

Os eventos que chegam ao cliente sao filtrados pelo array events_downstream do webhook: padroes com curinga (por exemplo flow.* ou node.token) compilados uma vez por sessao.

O SDK envolve toda a sessao em runWebhookStream, que nao tem o limite de 100 s do proxy:

import { runWebhookStream } from '@daguito/sdk'

const result = await runWebhookStream({
  apiUrl: 'https://api.daguito.com',
  webhookId: 'wh_abc123',
  token: 'dgsk_wh_...', // token de streaming
  input: { vision_image: '...' },
  timeoutMs: 300000, // 5 minutos — sin limite del proxy
})

console.log(result)
// { ok, executionId, status: 'completed'|'failed'|'unknown', output, elapsedMs }
Atenção

A reconexao nao e automatica. Cada socket se autentica separadamente com o token, entao seu cliente deve reconectar e reabrir a sessao se a conexao cair.

Eventos de fluxo

Tanto os callbacks quanto o WebSocket entregam FlowEvent: o mesmo modelo de eventos que percorre a execucao de um fluxo do inicio ao fim.

Tipos de evento

O envelope do evento

export type EventType =
  | 'flow.started' | 'flow.resumed' | 'flow.completed'
  | 'flow.failed'  | 'flow.paused'  | 'flow.branch'
  | 'node.started' | 'node.completed' | 'node.progress'
  | 'node.emit'    | 'node.token'
  | 'merge.progress' | 'handoff.requested'

export interface FlowEvent {
  type: EventType
  session_key: string
  flow_id?: string                // uuid
  node_id?: string
  branch_id?: string
  at: string                      // timestamp ISO
  data: Record<string, unknown>   // la forma depende del tipo
}

Filtros com curinga

Tanto events (callbacks) quanto events_downstream (streaming) aceitam padroes com curinga. O ponto separa segmentos e o asterisco atua como curinga:

'flow.completed'   // coincidencia exacta
'flow.*'           // todos los eventos de flow
'node.*'           // todos los eventos de node
'*'                // todos los eventos

Configuracao e limites

A configuracao do webhook vive em config e depende do tipo. Estes sao os principais limites:

export type WebhookType   = 'http' | 'streaming'
export type WebhookStatus = 'active' | 'paused' | 'revoked'

export interface Webhook {
  id: string                 // wh_*
  org_id: string
  flow_id: string            // uuid
  name: string               // 1-200 chars
  type: WebhookType          // fija el transporte
  token_preview: string      // dgsk_wh_...LAST4
  status: WebhookStatus
  config: {
    http?: {
      timeout_ms: number     // 1-60000, default 30000
      outbound_callbacks: OutboundCallback[]
    }
    streaming?: {
      audio_format?: string
      sample_rate?: number
      events_downstream: string[] // requerido, patrones con comodin
    }
    audio?: {
      max_session_seconds?: number
      max_bytes_per_session?: number
      max_concurrent_per_webhook?: number // 0 = sin tope
    }
  }
  last_used_at: string | null
}
Atenção

Em webhooks de streaming, events_downstream e obrigatorio e nao tem valor padrao. Se voce o omitir, a lista de permitidos fica vazia e o cliente nao recebera nenhum evento.

Auditoria e observabilidade

Cada entrega de saída é registrada para que você possa depurar e tentar novamente. O Daguito mantém um histórico de entregas que você pode inspecionar.

Info

Cada entrega com falha fica registrada com seu status e o horário da próxima tentativa. A nova tentativa automática está no nosso roadmap.

O que vem a seguir?