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.
webhook · suas APIs viram toolsApesar 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.
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 card | Vira |
|---|---|
Rota consultar_pedido | Tool consultar_pedido, com schema próprio de parâmetros |
description + whenToUse + responseDescription | A descrição que o modelo lê para decidir se chama |
parameters[] | As propriedades do JSON Schema da tool (com required) |
enabled: false | A 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 Pedido → consultar_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).
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.
{description}
WHEN TO USE:
{whenToUse}
RESPONSE:
{responseDescription}{description} ({howToObtain}) Exemplo: {example}Uma rota bem descrita chega ao modelo assim:
{
"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.
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 |
|---|---|
path | Substituição textual no caminho: /orders/{{order_id}} → /orders/PED-10492. |
queryParams | Substituição textual em cada **valor** do objeto: {"cpf": "{{cpf}}"}. Os valores precisam ser strings. |
bodyTemplate | Substituiçã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.
No corpo, o placeholder com aspas vira valor JSON; sem aspas, número e JSON entram crus. A regra por tipo:
| Tipo do parâmetro | Escreva assim | Chega 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. |
{
"cliente_id": "{{cliente_id}}",
"observacao": "{{observacao}}",
"itens": {{itens}},
"quantidade": {{quantidade}},
"urgente": {{urgente}}
}| Situação | O que o runtime faz |
|---|---|
| Parâmetro **opcional** que o modelo não preencheu | O placeholder vira null no corpo, e a entrada é **removida** da query string — nada de {{param}} literal chegando à sua API. |
| Placeholder no **path** sem valor | Erro claro ao modelo listando os parâmetros que faltam (parâmetro de path deve ser obrigatório). |
| Template que não vira JSON válido | Erro explícito ao modelo com a posição do problema — o corpo **nunca** é enviado como texto quebrado. |
A autenticação é do card inteiro — vale para todas as rotas.
auth.type | Campos | O que vai na requisição |
|---|---|---|
none | — | Nada. |
bearer | token | Authorization: Bearer <token> |
api_key | apiKey, apiKeyName, apiKeyLocation | Header <apiKeyName>: <apiKey> (padrão) ou, com apiKeyLocation: "query", ?<apiKeyName>=<apiKey>. |
basic | username, password | Authorization: Basic <base64(user:senha)> |
custom_header | customHeaderName, customHeaderValue | O 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/json → config.defaultHeaders → headers 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.
| Comportamento | Detalhe |
|---|---|
| Timeout | config.timeout em **milissegundos** (padrão 30000), aplicado à chamada inteira. |
| Retentativas | Só com retryOnError: true (padrão do builder). maxRetries padrão 2 → até 3 tentativas, com espera de 1 s e 2 s. |
| O que é retentado | Apenas falhas de **rede**: conexão recusada, timeout, exceção do cliente HTTP. |
| O que **não** é retentado | Resposta HTTP com status ≥ 400 — volta na hora, sem nova tentativa. |
| Resposta com sucesso | JSON é devolvido formatado ao modelo; resposta não-JSON vai como texto, truncada em **5.000** caracteres. |
| Resposta com erro | Error: 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.
namestringobrigatórioNome do card no canvas (ex.: `API de Pedidos`). Não afeta os nomes das tools.
config.baseUrlstringobrigatórioURL base da API (ex.: `https://api.loja.com/v1`). A barra final é removida antes de concatenar com o `path`.
config.descriptionstringDescrição da API para quem edita o card.
config.authobjectpadrão: { type: "none" }Autenticação de todas as rotas — ver a tabela acima.
config.defaultHeadersobjectHeaders aplicados a todas as rotas. Só configurável por API/JSON.
config.timeoutnumber (ms)padrão: 30000Timeout de cada chamada.
config.retryOnErrorbooleanpadrão: trueRetenta falhas de rede.
config.maxRetriesnumberpadrão: 2Máximo de retentativas (até 3 tentativas no total).
config.routes[]WebhookRoute[]obrigatórioAs rotas — cada uma vira uma tool.
namestringobrigatórioNome da tool, sanitizado (`criar_pedido`). Único no workflow inteiro.
descriptionstringobrigatórioO que a rota faz — primeira linha da descrição que o modelo lê.
whenToUsestringobrigatórioQuando usar. Entra como `WHEN TO USE:` na descrição — é o que separa rotas parecidas.
methodGET | POST | PUT | PATCH | DELETEobrigatóriopadrão: GETMétodo HTTP. Só `POST`/`PUT`/`PATCH` enviam corpo.
pathstringobrigatórioCaminho 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.
queryParamsobjectQuery string fixa ou com `{{placeholders}}`. Só configurável por API/JSON.
headersobjectHeaders específicos desta rota. Só configurável por API/JSON.
responseDescriptionstringO que a resposta contém. Entra como `RESPONSE:` na descrição da tool.
enabledbooleanpadrão: trueDesliga a rota sem apagá-la — ela deixa de ser registrada como tool.
namestringobrigatórioNome usado em `{{nome}}` e no schema da tool. Minúsculo, sem acento, com `_`.
typestring | number | boolean | array | objectpadrão: stringTipo declarado no JSON Schema da tool.
requiredbooleanpadrão: trueEntra na lista `required` do schema — o modelo não pode omitir.
descriptionstringobrigatórioO que é o parâmetro, na voz de quem instrui o modelo.
howToObtainstringComo o agente consegue o valor (ex.: "peça ao cliente" ou "use `buscar_cliente` antes"). Vai entre parênteses na descrição.
examplestringUm valor de exemplo — vira `Exemplo: ...` no fim da descrição. Reduz muito o erro de formato.
defaultValuestringValor 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.
| Campo | O que faz |
|---|---|
responseMapping | Extrai 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). |
maxRequestsPerMinute | Limite de chamadas por minuto do card (janela deslizante). Estourou, o modelo recebe um erro claro pedindo para aguardar. |
dependsOn / triggers | Viram 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ê.
| Ponto | Como funciona |
|---|---|
| Quem enxerga as tools | Só 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 mais | No 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. |
| Rede | O 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). |
| Credenciais | Ficam 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ção | Um 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). |
| Custo | A 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.
O Webhook raramente aparece sozinho. As combinações que mais rendem:
| Com | Por 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. |
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.
- 1
Arraste o card Webhook para o canvas
Ele está em Ferramentas, na paleta da esquerda.
- 2
Conecte no slot do Agent
A saída da direita do Webhook entra em um
slot-Ndo Agent. Os slots crescem sozinhos — sempre há um livre. - 3
Preencha a conexão
Nome do card,
baseUrl, autenticação e timeout. Vale testar a URL base numcurlantes. - 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
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
Teste no sandbox
Rode o agente no próprio editor e observe a chamada. O evento
tool_usemostra exatamente o que o modelo mandou. - 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.
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".
{
"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" }
]
}
}# 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.
| Sintoma | Causa provável |
|---|---|
| O agente nunca chama a rota | whenToUse 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 allowed | A 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 reached | maxRequestsPerMinute do card estourou na janela de 1 minuto. |
Error: Route 'x' not found | O nome mudou depois de publicado, ou a rota está em outro card. A mensagem lista as rotas disponíveis. |
Error: Route 'x' is disabled | enabled: false na rota. |
| A API recebe a chamada sem o parâmetro | Rota 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 null | Parâ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 trocado | Nomes que colidem após a sanitização (Criar-Pedido e criar pedido viram o mesmo criar_pedido). |
Error: Timeout mesmo com a API rápida | timeout está em milissegundos: 30 significa 0,03 s. |
| A resposta chega cortada | Corpo 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.

