Ir para o conteúdo

Webhook

O card que transforma qualquer API HTTP em ferramentas do agente: cada rota vira uma tool. Referência completa, regras de substituição, combinações com outros cards e aplicações prontas.

Atualizado em 01 de set. de 2026

O Webhook é a ponte entre o agente e o seu sistema. Você descreve uma API — URL base, autenticação e um conjunto de rotas — e cada rota vira uma ferramenta que o modelo pode chamar sozinho, na hora certa, com os parâmetros certos.

É o card que faz o agente sair da conversa e agir: consultar um pedido, abrir um chamado, agendar, cobrar, atualizar um CRM.

Ferramentas · webhook · suas APIs viram tools

Apesar do nome, este card não recebe chamadas de fora: ele faz chamadas HTTP para a sua API. Quem recebe eventos do agente é o stream do run.

Cada rota vira uma tool

Um card Webhook com quatro rotas entrega quatro ferramentas ao agente — não uma ferramenta genérica de "chamar API". O modelo escolhe entre elas pelo nome e pela descrição, exatamente como escolheria entre a busca na web e a calculadora.

No cardVira
Rota consultar_pedidoTool consultar_pedido, com schema próprio de parâmetros
description + whenToUse + responseDescriptionA descrição que o modelo lê para decidir se chama
parameters[]As propriedades do JSON Schema da tool (com required)
enabled: falseA rota não é registrada — some da lista de tools

O nome da rota é sanitizado

O nome vira o nome da tool depois de perder acentos, virar minúsculo e trocar espaços/hífens por _ (máx. 64 caracteres): Consultar Pedidoconsultar_pedido. Nomes que colidem se sobrescrevem — entre rotas do mesmo card, entre dois cards Webhook do mesmo workflow, e com as ferramentas nativas (não batize uma rota de summarize ou web_search).

O que o modelo vê

Esta é a parte que decide se o agente acerta ou erra a chamada. O runtime monta a descrição da tool a partir de três campos da rota, e a descrição de cada parâmetro a partir de outros três. Não é decoração: é o único contexto que o modelo tem.

descrição da tool, como é montada
{description}

WHEN TO USE:
{whenToUse}

RESPONSE:
{responseDescription}
descrição de cada parâmetro
{description} ({howToObtain}) Exemplo: {example}

Uma rota bem descrita chega ao modelo assim:

a tool que o modelo recebejson
{
  "name": "consultar_pedido",
  "description": "Consulta o status e os itens de um pedido pelo número.\n\nWHEN TO USE:\nUse quando o cliente perguntar sobre um pedido, entrega, rastreio ou nota fiscal.\n\nRESPONSE:\nRetorna status, data de envio, código de rastreio e a lista de itens.",
  "input_schema": {
    "type": "object",
    "properties": {
      "order_id": {
        "type": "string",
        "description": "Número do pedido do cliente (peça ao cliente ou obtenha com buscar_pedidos_do_cliente) Exemplo: PED-10492"
      }
    },
    "required": ["order_id"]
  }
}

Escreva o whenToUse como uma regra, não como um resumo

"Consulta pedidos" não ajuda o modelo a decidir. "Use quando o cliente citar um número de pedido ou perguntar por entrega, rastreio ou nota fiscal — não use para trocas, que têm rota própria" resolve a ambiguidade entre rotas parecidas.

Como os parâmetros viram requisição

O modelo devolve os parâmetros como um objeto. O runtime não adivinha onde cada um entra: você diz, usando {{nome}} no lugar certo da rota.

Onde você escreve {{param}}O que acontece
pathSubstituição textual no caminho: /orders/{{order_id}}/orders/PED-10492.
queryParamsSubstituição textual em cada **valor** do objeto: {"cpf": "{{cpf}}"}. Os valores precisam ser strings.
bodyTemplateSubstituição no JSON do corpo, em POST/PUT/PATCH — com uma regra de aspas (abaixo).
Nenhum lugar (POST sem bodyTemplate)Os parâmetros viram o corpo da requisição **inteiro**, como o modelo os mandou.
Nenhum lugar (GET/DELETE)O parâmetro é **descartado** — não vira query string sozinho.

O erro nº 1 em rotas GET

Em GET e DELETE os parâmetros não viram query string automaticamente. Um parâmetro cpf numa rota GET /clientes some da chamada: para ele chegar, coloque-o no caminho (/clientes/{{cpf}}) ou declare queryParams: {"cpf": "{{cpf}}"}. A chamada "funciona", devolve a lista inteira, e o agente responde com o cliente errado.

A regra das aspas no bodyTemplate

No corpo, o placeholder com aspas vira valor JSON; sem aspas, número e JSON entram crus. A regra por tipo:

Tipo do parâmetroEscreva assimChega na sua API como
string"{{texto}}""valor" — string JSON, com escape correto de aspas e quebras de linha.
number{{quantidade}} (sem aspas)3 — número. Com aspas viraria a string "3".
boolean{{flag}} (tanto faz)true/false — JSON real, nas duas formas.
array / object{{itens}} (tanto faz)["A1","B2"] — o JSON real, nas duas formas.
bodyTemplatejson
{
  "cliente_id": "{{cliente_id}}",
  "observacao": "{{observacao}}",
  "itens": {{itens}},
  "quantidade": {{quantidade}},
  "urgente": {{urgente}}
}
SituaçãoO que o runtime faz
Parâmetro **opcional** que o modelo não preencheuO placeholder vira null no corpo, e a entrada é **removida** da query string — nada de {{param}} literal chegando à sua API.
Placeholder no **path** sem valorErro claro ao modelo listando os parâmetros que faltam (parâmetro de path deve ser obrigatório).
Template que não vira JSON válidoErro explícito ao modelo com a posição do problema — o corpo **nunca** é enviado como texto quebrado.

Autenticação

A autenticação é do card inteiro — vale para todas as rotas.

auth.typeCamposO que vai na requisição
noneNada.
bearertokenAuthorization: Bearer <token>
api_keyapiKey, apiKeyName, apiKeyLocationHeader <apiKeyName>: <apiKey> (padrão) ou, com apiKeyLocation: "query", ?<apiKeyName>=<apiKey>.
basicusername, passwordAuthorization: Basic <base64(user:senha)>
custom_headercustomHeaderName, customHeaderValueO header que você definir — para APIs com esquema próprio.

Os headers da chamada são montados nesta ordem, cada camada sobrescrevendo a anterior: Content-Type/Accept: application/jsonconfig.defaultHeadersheaders da rota → header de autenticação.

O token não chega ao modelo

A configuração do card (incluindo auth) é explicitamente excluída do input que vai para o modelo e para o evento tool_use do stream — o runtime injeta a config direto na chamada HTTP. O modelo só vê os parâmetros da rota; quem acompanha o run pelo SSE também. Suas credenciais não vazam para o histórico da conversa.

Execução: timeout, retentativas e erros

ComportamentoDetalhe
Timeoutconfig.timeout em **milissegundos** (padrão 30000), aplicado à chamada inteira.
RetentativasSó com retryOnError: true (padrão do builder). maxRetries padrão 2 → até 3 tentativas, com espera de 1 s e 2 s.
O que é retentadoApenas falhas de **rede**: conexão recusada, timeout, exceção do cliente HTTP.
O que **não** é retentadoResposta HTTP com status ≥ 400 — volta na hora, sem nova tentativa.
Resposta com sucessoJSON é devolvido formatado ao modelo; resposta não-JSON vai como texto, truncada em **5.000** caracteres.
Resposta com erroError: HTTP 404 - <corpo>, com o corpo truncado em **500** caracteres.

O erro vira contexto, não exceção

Todo erro volta ao modelo como texto (Error: Route 'x' is disabled, Error: Webhook base URL not configured, Error: HTTP 422 - ...). O agente não quebra: ele lê o erro e decide — tentar outra rota, pedir um dado que faltou, ou explicar ao usuário. Por isso vale caprichar nas mensagens de erro da sua API: elas viram instrução para o modelo.

Referência: o card

namestringobrigatório

Nome do card no canvas (ex.: `API de Pedidos`). Não afeta os nomes das tools.

config.baseUrlstringobrigatório

URL base da API (ex.: `https://api.loja.com/v1`). A barra final é removida antes de concatenar com o `path`.

config.descriptionstring

Descrição da API para quem edita o card.

config.authobjectpadrão: { type: "none" }

Autenticação de todas as rotas — ver a tabela acima.

config.defaultHeadersobject

Headers aplicados a todas as rotas. Só configurável por API/JSON.

config.timeoutnumber (ms)padrão: 30000

Timeout de cada chamada.

config.retryOnErrorbooleanpadrão: true

Retenta falhas de rede.

config.maxRetriesnumberpadrão: 2

Máximo de retentativas (até 3 tentativas no total).

config.routes[]WebhookRoute[]obrigatório

As rotas — cada uma vira uma tool.

Referência: a rota

namestringobrigatório

Nome da tool, sanitizado (`criar_pedido`). Único no workflow inteiro.

descriptionstringobrigatório

O que a rota faz — primeira linha da descrição que o modelo lê.

whenToUsestringobrigatório

Quando usar. Entra como `WHEN TO USE:` na descrição — é o que separa rotas parecidas.

methodGET | POST | PUT | PATCH | DELETEobrigatóriopadrão: GET

Método HTTP. Só `POST`/`PUT`/`PATCH` enviam corpo.

pathstringobrigatório

Caminho concatenado à `baseUrl`. Aceita `{{parametros}}`.

parameters[]WebhookParam[]

Os parâmetros que o modelo preenche — viram o JSON Schema da tool.

bodyTemplatestring (JSON)

Corpo com `{{placeholders}}`. Sem ele, os parâmetros viram o corpo inteiro.

queryParamsobject

Query string fixa ou com `{{placeholders}}`. Só configurável por API/JSON.

headersobject

Headers específicos desta rota. Só configurável por API/JSON.

responseDescriptionstring

O que a resposta contém. Entra como `RESPONSE:` na descrição da tool.

enabledbooleanpadrão: true

Desliga a rota sem apagá-la — ela deixa de ser registrada como tool.

Referência: o parâmetro

namestringobrigatório

Nome usado em `{{nome}}` e no schema da tool. Minúsculo, sem acento, com `_`.

typestring | number | boolean | array | objectpadrão: string

Tipo declarado no JSON Schema da tool.

requiredbooleanpadrão: true

Entra na lista `required` do schema — o modelo não pode omitir.

descriptionstringobrigatório

O que é o parâmetro, na voz de quem instrui o modelo.

howToObtainstring

Como o agente consegue o valor (ex.: "peça ao cliente" ou "use `buscar_cliente` antes"). Vai entre parênteses na descrição.

examplestring

Um valor de exemplo — vira `Exemplo: ...` no fim da descrição. Reduz muito o erro de formato.

defaultValuestring

Valor sugerido quando o modelo não informa. Documentado no catálogo; hoje **não** é preenchido automaticamente pelo runtime — mencione o padrão na `description` e declare o parâmetro como obrigatório.

validValuesstring[]

Valores aceitos. Idem: descreva-os na `description` para o modelo respeitar.

Recursos avançados das rotas

CampoO que faz
responseMappingExtrai da resposta só os campos declarados, por caminho ({"pedido": "$.order.id", "sku": "$.items[0].sku"}) — menos tokens no contexto. Se nenhum caminho casar, a resposta completa é devolvida (mapping errado não esconde o dado).
maxRequestsPerMinuteLimite de chamadas por minuto do card (janela deslizante). Estourou, o modelo recebe um erro claro pedindo para aguardar.
dependsOn / triggersViram instrução de encadeamento na descrição que o modelo lê: DEPENDS ON: call first: buscar_cliente / AFTER THIS: consider calling: rastrear.

Ainda sem efeito

responseExample e parameters[].in são aceitos e ignorados (o lugar do parâmetro é definido pelo {{placeholder}} no path/query/corpo). defaultValue/validValues não são aplicados automaticamente — descreva o padrão e os valores aceitos na description do parâmetro, que é o que o modelo lê.

Escopo, segurança e limites

PontoComo funciona
Quem enxerga as toolsSó o agente em cujo **slot** o card está ligado (ou que o recebe por pipeline) — o mesmo escopo dos cards MCP. Ligue o card em mais de um slot para compartilhar.
Como restringir ainda maisNo card Agent, config.tools_filter com a lista de nomes permitidos: quando dois agentes dividem o mesmo card, cada um expõe só o subconjunto da sua especialidade.
RedeO runtime **bloqueia destino privado/loopback/metadata** — um card só alcança endereços públicos (instalações que precisam de API interna liberam hosts específicos por AGENT_EGRESS_ALLOW).
CredenciaisFicam na config do nó, nunca no prompt nem no stream. Ainda assim, use uma chave **de escopo mínimo** — o agente pode chamar qualquer rota habilitada.
ValidaçãoUm card Webhook sem baseUrl **e** sem routes reprova na validação do workflow (webhook_no_config) — ver [Validar, sandbox e publicar](/pt/api/agents/publish).
CustoA chamada HTTP não é cobrada; o que pesa é o retorno entrar no contexto do modelo. Respostas enormes = mais tokens em toda rodada seguinte.

Rotas que mudam o mundo pedem confirmação

Para POST/DELETE que cobram, cancelam ou apagam, coloque um card Aprovação do Usuário antes do trecho do fluxo que as usa — ou exija um parâmetro de confirmação explícito e diga no whenToUse que ele só vem do usuário.

Combina com

O Webhook raramente aparece sozinho. As combinações que mais rendem:

ComPor quê
[Agent](/pt/api/agents/cards/essentials)O básico: as rotas viram tools no slot do agente. Descreva no system_prompt a política de uso ("nunca invente número de pedido").
[Transformar](/pt/api/agents/cards/data)Em pipeline depois do Webhook, extrai só os campos que interessam da resposta JSON — menos tokens e menos distração para o próximo passo. É o substituto prático do responseMapping.
[Conhecimento (RAG)](/pt/api/agents/cards/tools)RAG responde "como funciona a política de troca"; Webhook responde "qual o status do **seu** pedido". Juntos cobrem conhecimento estático e dado vivo.
[Aprovação do Usuário](/pt/api/agents/cards/logic)Trava humana antes de rotas que gastam dinheiro ou apagam dados.
[Se / Senão](/pt/api/agents/cards/logic)Ramifica pelo resultado: pedido entregue segue para pesquisa de satisfação, atrasado vai para o time de logística.
[Roteador](/pt/api/agents/cards/coordination)Um agente por domínio (pedidos, financeiro, suporte), cada um com o seu card Webhook e o seu tools_filter.
[Paralelo](/pt/api/agents/cards/coordination)Consulta várias APIs ao mesmo tempo (estoque + frete + crédito) e junta as respostas numa só.
[Início](/pt/api/agents/cards/essentials)Variáveis do run ({{customer_id}}, {{tenant}}) chegam pelo variables da chamada e identificam o usuário final na sua API.

O que dá para construir

Rotas: consultar_pedido (GET), abrir_chamado (POST), solicitar_troca (POST).

O agente usa RAG para a política de trocas e o Webhook para o caso concreto. whenToUse separa "dúvida sobre a regra" de "quero trocar o meu". A troca passa por Aprovação do Usuário antes do POST.

Rotas: listar_horarios (GET com queryParams de data), agendar (POST com bodyTemplate), cancelar (DELETE com {{id}} no path).

O howToObtain de slot_id diz "use listar_horarios primeiro" — o encadeamento que o dependsOn prometeria, feito por texto.

Rotas: buscar_empresa (GET), criar_lead (POST), atualizar_estagio (PATCH).

O agente conversa, enriquece com busca na web, cria o lead e move o estágio. Um card Transformar depois do buscar_empresa reduz a resposta do CRM aos 5 campos que importam.

Rotas: status_do_servico (GET), reprocessar_fila (POST), escalar_plantao (POST).

Aqui o tools_filter é obrigatório: só o agente "operador" enxerga as rotas de escrita; o agente de primeiro nível fica com as de leitura.

Rotas: consultar_fatura (GET), gerar_segunda_via (POST), registrar_promessa_pagamento (POST).

Respostas curtas e objetivas no responseDescription — em rotas financeiras, o modelo deve repetir valores, nunca recalculá-los. Se precisar de conta, conecte também a Calculadora.

No builder, passo a passo

  1. 1

    Arraste o card Webhook para o canvas

    Ele está em Ferramentas, na paleta da esquerda.

  2. 2

    Conecte no slot do Agent

    A saída da direita do Webhook entra em um slot-N do Agent. Os slots crescem sozinhos — sempre há um livre.

  3. 3

    Preencha a conexão

    Nome do card, baseUrl, autenticação e timeout. Vale testar a URL base num curl antes.

  4. 4

    Crie a primeira rota

    Nome, método, caminho, descrição e quando usar. Comece por uma rota de leitura (GET) — é a mais fácil de validar.

  5. 5

    Declare os parâmetros

    Nome, tipo, obrigatoriedade e descrição. Use {{nome}} no caminho ou no corpo, senão o valor não chega à API.

  6. 6

    Teste no sandbox

    Rode o agente no próprio editor e observe a chamada. O evento tool_use mostra exatamente o que o modelo mandou.

  7. 7

    Publique

    Só a versão publicada responde no /run.

O builder edita um subconjunto

O modal do card edita nome, baseUrl, descrição, autenticação, timeout e as rotas (nome, método, caminho, descrição, quando usar, bodyTemplate, responseDescription e parâmetros). Campos como defaultHeaders, queryParams, headers por rota, retryOnError/maxRetries e howToObtain/example funcionam no runtime, mas hoje só entram pelo JSON do workflow, via API.

Pela API

O card Webhook é um nó webhook no JSON do workflow. Ele não entra no fluxo: liga-se ao agente por uma edge com targetHandle: "slot-N".

agente de atendimento com duas rotasjson
{
  "name": "Atendimento Loja",
  "workflow": {
    "nodes": [
      { "id": "start",   "type": "start", "data": { "label": "Início", "variables": [
          { "name": "customer_id", "type": "input", "required": true,
            "description": "Cliente autenticado na sua aplicação" }
      ] } },
      { "id": "agent-1", "type": "agent", "data": {
          "name": "Atendente",
          "model": "hinow/himax",
          "system_prompt": "Você atende clientes da loja. Nunca invente número de pedido: consulte sempre. O cliente atual é {{customer_id}}.",
          "config": { "tools_filter": ["consultar_pedido", "abrir_chamado"] }
      } },
      { "id": "hook-1",  "type": "webhook", "data": {
          "name": "API da Loja",
          "config": {
            "baseUrl": "https://api.loja.com/v1",
            "timeout": 15000,
            "retryOnError": true,
            "maxRetries": 2,
            "auth": { "type": "bearer", "token": "sk_loja_..." },
            "defaultHeaders": { "X-Origem": "agente-hinow" },
            "routes": [
              {
                "id": "r1",
                "name": "consultar_pedido",
                "description": "Consulta status e itens de um pedido pelo número.",
                "whenToUse": "Use quando o cliente citar um número de pedido ou perguntar por entrega, rastreio ou nota fiscal.",
                "responseDescription": "Retorna status, data de envio, código de rastreio e itens.",
                "method": "GET",
                "path": "/orders/{{order_id}}",
                "queryParams": { "customer_id": "{{customer_id}}" },
                "parameters": [
                  { "id": "p1", "name": "order_id", "type": "string", "required": true,
                    "description": "Número do pedido",
                    "howToObtain": "Peça ao cliente; ele aparece no e-mail de confirmação",
                    "example": "PED-10492" },
                  { "id": "p2", "name": "customer_id", "type": "string", "required": true,
                    "description": "Id do cliente autenticado", "example": "cust_881" }
                ],
                "enabled": true
              },
              {
                "id": "r2",
                "name": "abrir_chamado",
                "description": "Abre um chamado de suporte vinculado a um pedido.",
                "whenToUse": "Use quando o problema não puder ser resolvido pela consulta — produto avariado, atraso acima de 5 dias, cobrança indevida.",
                "responseDescription": "Retorna o número do chamado e o prazo de resposta.",
                "method": "POST",
                "path": "/tickets",
                "bodyTemplate": "{\"order_id\": \"{{order_id}}\", \"motivo\": \"{{motivo}}\", \"prioridade\": {{prioridade}}}",
                "parameters": [
                  { "id": "p3", "name": "order_id", "type": "string", "required": true,
                    "description": "Pedido relacionado",
                    "howToObtain": "Use consultar_pedido antes", "example": "PED-10492" },
                  { "id": "p4", "name": "motivo", "type": "string", "required": true,
                    "description": "Resumo do problema em uma frase" },
                  { "id": "p5", "name": "prioridade", "type": "number", "required": false,
                    "description": "1 a 5. Use 3 quando o cliente não indicar urgência", "example": "3" }
                ],
                "enabled": true
              }
            ]
          }
      } },
      { "id": "end-1",   "type": "end", "data": { "label": "Fim", "status": "success" } }
    ],
    "edges": [
      { "id": "e1", "source": "start",   "target": "agent-1" },
      { "id": "e2", "source": "hook-1",  "target": "agent-1", "targetHandle": "slot-1" },
      { "id": "e3", "source": "agent-1", "target": "end-1" }
    ]
  }
}
criar e rodarbash
# 1. cria o agent (o workflow passa pelo validador antes de salvar)
curl -X POST https://agents.hinow.ai/v1/agents \
  -H "Authorization: Bearer hi_SUA_API_KEY" \
  -H "Content-Type: application/json" \
  -d @workflow.json
# → {"agent": {"id": "agt_...", "status": "draft"}}

# 2. publica
curl -X POST https://agents.hinow.ai/v1/agents/agt_.../publish \
  -H "Authorization: Bearer hi_SUA_API_KEY"

# 3. roda — 'variables' alimenta o {{customer_id}} do Início
curl -X POST https://agents.hinow.ai/v1/agents/agt_.../run \
  -H "Authorization: Bearer hi_SUA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "cadê meu pedido PED-10492?",
       "variables": {"customer_id": "cust_881"},
       "stream": true}'

Acompanhe a chamada no stream

Cada chamada de rota aparece como um evento tool_use (com os parâmetros que o modelo escolheu) seguido do resultado — inclusive quando o resultado é Error: HTTP 500. É o jeito mais rápido de descobrir que um parâmetro não estava chegando. Ver Eventos da execução.

Quando algo não funciona

SintomaCausa provável
O agente nunca chama a rotawhenToUse vago ou parecido com o de outra rota; ou o card não está no **slot deste agente** (o escopo é por slot); ou tools_filter do agente não inclui o nome da rota.
Error: destination not allowedA baseUrl resolve para endereço privado/loopback/metadata — o runtime só alcança destinos públicos. Exponha a API publicamente ou libere o host via AGENT_EGRESS_ALLOW.
Error: rate limit reachedmaxRequestsPerMinute do card estourou na janela de 1 minuto.
Error: Route 'x' not foundO nome mudou depois de publicado, ou a rota está em outro card. A mensagem lista as rotas disponíveis.
Error: Route 'x' is disabledenabled: false na rota.
A API recebe a chamada sem o parâmetroRota GET/DELETE com o parâmetro fora do path e fora de queryParams — só o que aparece num template chega.
Error: bodyTemplate produced invalid JSON…O template, depois da substituição, não formou JSON — a mensagem aponta a posição. O corpo nunca é enviado quebrado.
Um campo chegou como nullParâmetro **opcional** que o modelo não preencheu — o runtime troca o placeholder por null para manter o JSON válido. Se o campo é essencial, declare-o obrigatório.
Duas rotas com comportamento trocadoNomes que colidem após a sanitização (Criar-Pedido e criar pedido viram o mesmo criar_pedido).
Error: Timeout mesmo com a API rápidatimeout está em milissegundos: 30 significa 0,03 s.
A resposta chega cortadaCorpo maior que 5.000 caracteres. Reduza na API ou encadeie um card Transformar.

Referência de todos os cards

Cada nó do builder, com campos, padrões e conexões.

Esta página foi útil?