Custom Tools (Ferramentas Personalizadas)
Guia completo para criar ferramentas personalizadas que conectam seu agente a qualquer API externa.
O que são Custom Tools?
Custom Tools permitem que seu agente execute chamadas HTTP para qualquer API externa durante uma conversa. Por exemplo:
- Criar um lead no seu CRM
- Consultar estoque de um produto
- Agendar uma visita no seu sistema
- Enviar dados para um webhook
Criando uma Custom Tool
No dashboard, acesse Integrações → Nova Custom Tool. Você pode criar manualmente ou colar um cURL para importar automaticamente.
Campos principais
| Campo | Descrição |
|---|---|
| Nome da função | Identificador único (ex: criar_lead). Só aceita letras, números e _. |
| Título | Nome amigável exibido no dashboard (ex: "Criar Lead") |
| Descrição | Explica para a IA quando e como usar a tool. Seja específico. |
| Método HTTP | GET, POST, PUT, PATCH ou DELETE |
| URL | Endpoint da API. Suporta interpolação com {{param}} |
| Headers | Headers HTTP customizados (ex: Content-Type, X-Api-Key) |
| Body Template | Template JSON do corpo da requisição (apenas para POST/PUT/PATCH) |
| Parâmetros | Schema JSON dos dados que o agente precisa coletar do usuário |
Tipos de valores no Body Template
O body_template suporta 3 tipos de valores, que podem ser combinados livremente:
| Tipo | Sintaxe | Origem |
|---|---|---|
| Hardcoded | Valor literal | Fixo no template, nunca muda |
| Do usuário (via LLM) | {{nome_do_parametro}} | O agente coleta na conversa |
| Do browser (Client Variable) | {{client.nome_da_variavel}} | Extraído automaticamente do navegador do visitante |
Exemplo combinando os 3 tipos
{
"source": "takesales",
"nome": "{{nome}}",
"email": "{{email}}",
"interesse": "{{interesse}}",
"user_id": "{{client.userId}}",
"page_url": "{{client.currentPage}}"
}Neste exemplo:
"source": "takesales"— hardcoded, sempre vai"takesales""nome","email","interesse"— coletados pelo agente na conversa (parâmetros do LLM)"user_id","page_url"— extraídos automaticamente do browser do visitante (client variables)
Valores Hardcoded
Para enviar um valor fixo que nunca muda, escreva o valor diretamente no template. Não inclua esse campo nos parâmetros — assim a IA não tenta preenchê-lo.
Exemplo: enviar origem fixa para o CRM
Body Template:
{
"source": "chatbot",
"campaign": "site-2024",
"nome": "{{nome}}",
"email": "{{email}}"
}Parâmetros (JSON Schema):
{
"type": "object",
"properties": {
"nome": {
"type": "string",
"description": "Nome completo do visitante"
},
"email": {
"type": "string",
"description": "Email do visitante"
}
},
"required": ["nome", "email"]
}O agente vai pedir nome e email na conversa. Os campos source e campaign vão sempre com os valores fixos.
Parâmetros do LLM (coletados na conversa)
Parâmetros definidos no JSON Schema são apresentados à IA como argumentos da função. O agente coleta esses dados naturalmente na conversa e os envia no {{placeholder}} correspondente.
Exemplo: consultar preço de produto
URL: https://api.exemplo.com/products/{{product_id}}/price
Método: GET
Parâmetros:
{
"type": "object",
"properties": {
"product_id": {
"type": "string",
"description": "Código do produto (ex: SKU-1234)"
}
},
"required": ["product_id"]
}O agente pergunta qual produto o visitante quer e usa o código para buscar o preço. O {{product_id}} é substituído na URL automaticamente.
Tipos de parâmetros suportados
| Tipo | Exemplo | Uso |
|---|---|---|
string | "nome": { "type": "string" } | Textos, emails, códigos |
number | "valor": { "type": "number" } | Valores numéricos |
integer | "quantidade": { "type": "integer" } | Números inteiros |
boolean | "aceita_termos": { "type": "boolean" } | Verdadeiro/falso |
enum | "tipo": { "type": "string", "enum": ["A", "B"] } | Lista de opções fixas |
Dicas para descrições de parâmetros
A descrição do parâmetro orienta a IA sobre o que pedir ao usuário. Seja específico:
{
"type": "object",
"properties": {
"telefone": {
"type": "string",
"description": "Telefone com DDD, formato (11) 99999-9999"
},
"tipo_imovel": {
"type": "string",
"enum": ["apartamento", "casa", "terreno", "comercial"],
"description": "Tipo de imóvel que o visitante procura"
},
"valor_maximo": {
"type": "number",
"description": "Orçamento máximo em reais (R$)"
}
},
"required": ["telefone", "tipo_imovel"]
}Client Variables (dados do browser)
Client Variables permitem capturar dados do navegador do visitante automaticamente, sem que o agente precise perguntar. Útil para enviar contexto como ID do usuário logado, página atual, dados do localStorage, etc.
Como configurar
- No dashboard, acesse Configurações do Agente → Client Variables
- Adicione variáveis com nome, fonte e chave
- No body template, use
{{client.nome_da_variavel}}
Fontes disponíveis
| Fonte | Exemplo de chave | O que captura |
|---|---|---|
localStorage | user_id | localStorage.getItem("user_id") |
sessionStorage | session_token | sessionStorage.getItem("session_token") |
cookie | _ga | Valor do cookie _ga |
window | user.profile.id | window.user.profile.id (suporta caminhos aninhados) |
querySelector | #user-name | textContent do elemento |
meta | user-id | <meta name="user-id" content="123"> |
Exemplo: capturar ID do usuário logado
Configuração da Client Variable:
| Nome | Fonte | Chave |
|---|---|---|
userId | localStorage | app_user_id |
userEmail | window | currentUser.email |
currentPage | window | location.href |
Body Template:
{
"user_id": "{{client.userId}}",
"user_email": "{{client.userEmail}}",
"page": "{{client.currentPage}}",
"mensagem": "{{mensagem}}"
}O widget extrai os valores do browser na conexão e o servidor injeta automaticamente nos args da tool com o prefixo client..
Interpolação na URL, Headers e Query Params
Os placeholders {{param}} e {{client.var}} funcionam em todos os campos da configuração do endpoint:
Na URL
https://api.exemplo.com/users/{{client.userId}}/notes
Parâmetros usados na URL são removidos automaticamente do body para evitar duplicação.
Nos Headers
X-Tenant-Id: {{client.tenantId}}
X-Request-Source: chatbot
Authorization: Bearer {{client.apiToken}}
Em Query Params (GET/DELETE)
Para métodos GET e DELETE, parâmetros que não foram usados na URL são enviados automaticamente como query string:
URL: https://api.exemplo.com/search
Método: GET
Parâmetros: query, category
Resultado: https://api.exemplo.com/search?query=valor&category=imoveis
Casos de uso completos
1. Criar lead no CRM (POST com dados mistos)
Cenário: O agente coleta dados do visitante e envia para o CRM. O ID do usuário logado e a origem são enviados automaticamente.
Configuração:
- Função:
criar_lead_crm - Descrição: "Cria um novo lead no CRM quando o visitante demonstra interesse. Colete nome, email e telefone antes de chamar."
- Método: POST
- URL:
https://crm.exemplo.com/api/v1/leads - Headers:
X-Api-Key: sua-chave-aqui
Parâmetros:
{
"type": "object",
"properties": {
"nome": { "type": "string", "description": "Nome completo" },
"email": { "type": "string", "description": "Email de contato" },
"telefone": { "type": "string", "description": "Telefone com DDD" },
"interesse": { "type": "string", "description": "O que o visitante procura" }
},
"required": ["nome", "email"]
}Body Template:
{
"name": "{{nome}}",
"email": "{{email}}",
"phone": "{{telefone}}",
"notes": "{{interesse}}",
"source": "takesales-chatbot",
"channel": "website",
"referred_by": "{{client.currentPage}}",
"external_id": "{{client.userId}}"
}Client Variables:
| Nome | Fonte | Chave |
|---|---|---|
currentPage | window | location.href |
userId | localStorage | user_id |
2. Consultar estoque (GET com parâmetro na URL)
Cenário: O visitante pergunta se um produto está disponível. O agente busca no sistema de estoque.
Configuração:
- Função:
consultar_estoque - Descrição: "Consulta disponibilidade de um produto pelo código SKU. Use quando o visitante perguntar sobre disponibilidade ou estoque."
- Método: GET
- URL:
https://api.exemplo.com/inventory/{{sku}} - Headers:
Authorization: Bearer token-fixo-aqui
Parâmetros:
{
"type": "object",
"properties": {
"sku": {
"type": "string",
"description": "Código SKU do produto (ex: PROD-001)"
}
},
"required": ["sku"]
}Body Template: (vazio — é GET)
3. Enviar webhook com contexto completo
Cenário: Dispara um webhook para seu sistema quando o visitante quer falar com um humano.
Configuração:
- Função:
solicitar_atendimento_humano - Descrição: "Solicita atendimento humano quando o visitante pede para falar com uma pessoa. Colete um resumo do problema antes de chamar."
- Método: POST
- URL:
https://hooks.exemplo.com/webhook/atendimento
Parâmetros:
{
"type": "object",
"properties": {
"resumo": {
"type": "string",
"description": "Resumo do problema ou motivo do contato"
},
"urgencia": {
"type": "string",
"enum": ["baixa", "media", "alta"],
"description": "Nível de urgência"
}
},
"required": ["resumo"]
}Body Template:
{
"type": "human_handoff",
"summary": "{{resumo}}",
"urgency": "{{urgencia}}",
"visitor_email": "{{client.visitorEmail}}",
"visitor_name": "{{client.visitorName}}",
"page_url": "{{client.pageUrl}}",
"timestamp": "auto"
}Client Variables:
| Nome | Fonte | Chave |
|---|---|---|
visitorEmail | localStorage | visitor_email |
visitorName | localStorage | visitor_name |
pageUrl | window | location.href |
4. Atualizar registro existente (PUT com ID na URL)
Cenário: O agente atualiza o status de um pedido no sistema do cliente.
Configuração:
- Função:
atualizar_status_pedido - Descrição: "Atualiza o status de um pedido. Pergunte o número do pedido e o novo status desejado."
- Método: PUT
- URL:
https://api.exemplo.com/orders/{{order_id}}/status - Headers:
X-Api-Key: chave-fixa
Parâmetros:
{
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "Número do pedido (ex: PED-12345)"
},
"status": {
"type": "string",
"enum": ["processing", "shipped", "delivered", "cancelled"],
"description": "Novo status do pedido"
},
"motivo": {
"type": "string",
"description": "Motivo da alteração (opcional)"
}
},
"required": ["order_id", "status"]
}Body Template:
{
"status": "{{status}}",
"reason": "{{motivo}}",
"updated_by": "ai-agent",
"tenant_id": "{{client.tenantId}}"
}Note que order_id é usado na URL e automaticamente removido do body.
5. Buscar no catálogo e exibir como carrossel (GET com query params)
Cenário: O agente busca itens na base do cliente (produtos, imóveis, cursos, veículos — qualquer catálogo) e mostra os resultados como cards no chat.
Configuração:
- Função:
buscar_catalogo - Descrição: "Busca itens disponíveis com filtros. Pergunte categoria, cidade e faixa de preço."
- Método: GET
- URL:
https://api.exemplo.com/catalog
Parâmetros:
{
"type": "object",
"properties": {
"category": { "type": "string", "description": "Categoria do item" },
"city": { "type": "string", "description": "Cidade (ex: São Paulo, Curitiba)" },
"max_price": { "type": "number", "description": "Preço máximo em reais" }
},
"required": ["category"]
}Body Template: (vazio — método GET)
Para GET, os parâmetros são enviados automaticamente como query string:
https://api.exemplo.com/catalog?category=sofa&city=São+Paulo&max_price=5000
Exibir como carrossel: na aba Resposta, ligue "Exibir resultado como carrossel" e informe os caminhos (veja a seção abaixo).
6. Tool 100% automática (sem parâmetros do LLM)
Cenário: O agente envia dados do visitante para analytics sem coletar nada na conversa.
Configuração:
- Função:
registrar_interesse - Descrição: "Registra que o visitante demonstrou interesse. Chame automaticamente quando o visitante pedir mais informações sobre um produto."
- Método: POST
- URL:
https://hooks.exemplo.com/analytics/interest
Parâmetros:
{
"type": "object",
"properties": {},
"required": []
}Body Template:
{
"event": "interest_shown",
"visitor_id": "{{client.visitorId}}",
"page": "{{client.currentPage}}",
"referrer": "{{client.referrer}}",
"source": "ai-chatbot"
}Neste caso, tudo é hardcoded ou client variable. O agente chama a tool sem precisar pedir nenhum dado ao usuário.
Exibir resultado como carrossel
Qualquer tool que devolva uma lista pode ser exibida como cards no chat (título, imagem, preço, link). Na aba Resposta, ligue Exibir resultado como carrossel e informe os caminhos:
- Caminho da lista — a partir da raiz do resultado da tool. Para tools HTTP a raiz é
{ "success": true, "data": <corpo da resposta>, "status": 200 }, então quase sempre começa comdata.(ex:data.items). - Campos — a partir de cada item da lista.
TítuloeLinksão obrigatórios;Imagem,Subtítulo,DescriçãoePreçosão opcionais.
Os caminhos usam ponto e colchete (results[0].photos[0].url).
O que fica salvo na tool:
{
"display": {
"type": "carousel",
"items": "data.items",
"fields": {
"title": "name",
"url": "link",
"image": "photo",
"subtitle": "city",
"description": "summary",
"price": "price"
}
}
}O exemplo de resposta é obrigatório quando o carrossel está ligado: cole um JSON real da sua API no campo "Exemplo de resposta". Ao salvar, o backend aplica o mapeamento sobre esse JSON e recusa a tool (400) se o caminho da lista não resolver ou se nenhum item tiver título e link — o erro diz qual caminho falhou. O mesmo exemplo continua sendo incluído na descrição da tool para o agente.
O carrossel aparece no chat de texto e nas sessões de voz do widget. O resultado completo da API continua indo para o agente, que responde em texto normalmente.
Autenticação da API externa
A autenticação é configurada nas credenciais da integração "Custom" no workspace. Suporte automático:
| Método | Como configurar |
|---|---|
| Bearer token | Salve api_key nas credenciais — é adicionado automaticamente como Authorization: Bearer ... |
| Header customizado | Salve api_key + api_key_header nas credenciais (ex: X-Api-Key) |
| No header da tool | Adicione diretamente nos headers da tool: Authorization: Bearer token-fixo |
| No body | Inclua no body_template: "api_key": "valor-fixo" |
Evite colocar credenciais sensíveis diretamente no body template. Prefira usar as credenciais da integração (salvas de forma segura no workspace) ou os headers da tool. Valores no body template ficam visíveis para qualquer membro do workspace.
Nível de risco
Cada tool pode ter um nível de risco que controla se o agente executa automaticamente ou pede confirmação ao visitante:
| Nível | Comportamento |
|---|---|
auto | Executa automaticamente (padrão) |
confirm | Exibe confirmação ao visitante antes de executar |
Use confirm para ações destrutivas (deletar, cancelar) ou que envolvem dados sensíveis.
Como funciona a confirmação
Quando o agente tenta executar uma tool com nível confirm, o fluxo é:
- O agente pausa a execução e envia um card de confirmação inline no chat
- O visitante vê um card com o nome da tool, descrição e os parâmetros que serão enviados
- O visitante clica em Confirmar ou Cancelar
- Se confirmado, a tool é executada normalmente. Se cancelado, o agente recebe um aviso e pode continuar a conversa
No widget embutido, o card de confirmação já aparece automaticamente. Se você usa o SDK, precisa ouvir o evento
toolConfirmatione montar sua própria UI — veja a documentação do SDK.
Debugging e Logs
Verificando a execução de tools
Para inspecionar o que aconteceu em uma chamada de tool:
- Acesse Conversas no dashboard e abra a sessão desejada
- Na timeline da conversa, localize o step da tool — ele mostra o nome da tool chamada
- Expanda o step para ver os parâmetros enviados (request) e a resposta recebida (response) da API externa
Erros comuns
| Erro | Causa provável | Solução |
|---|---|---|
404 Not Found | URL incorreta ou recurso inexistente | Verifique a URL e os placeholders de interpolação |
401 Unauthorized | Credenciais inválidas ou expiradas | Atualize o token/API key nas credenciais da integração |
Timeout | API externa demorando para responder | A API externa deve responder em até 10 segundos; otimize o endpoint ou aumente a performance do servidor |
O que acontece quando uma tool falha
Quando a chamada HTTP retorna erro ou timeout, o agente recebe uma mensagem informando a falha. A partir disso, o agente pode:
- Informar o visitante que houve um problema e sugerir alternativas
- Tentar uma abordagem diferente (se houver outra tool disponível)
- Coletar os dados manualmente e prosseguir a conversa
O visitante nunca vê a resposta de erro crua da API — o agente sempre interpreta e responde de forma natural.
Salvando dados da resposta (aba Resposta)
A resposta da API não precisa morrer no turno em que foi chamada. Na aba Resposta da tool você pode:
Salvar valores em variáveis da conversa
Informe o nome da variável e o caminho na resposta (notação com ponto e colchete, com raiz no campo data):
| Variável | Caminho |
|---|---|
customer_id | customer.id |
first_item | customer.items[0].id |
Cada variável pode ser marcada como secreta (persistida mascarada como ***) e/ou persistente (continua valendo nas próximas conversas da mesma pessoa).
Vincular a pessoa da conversa
Informe o caminho das chaves que identificam o contato — a de maior precedência vence: ID externo > CPF > telefone E.164 > e-mail — e, opcionalmente, o caminho do nome completo. Com isso a conversa passa a apontar para o registro em Pessoas.
Só marque Identidade verificada se essa API exigir prova de identidade do visitante (token, OTP). Um endpoint que apenas ecoa uma chave digitada — por exemplo, busca por CPF informado no chat — não prova quem é o visitante, e tratá-lo como prova expõe dados de terceiros.
Resumo rápido
Hardcoded → "campo": "valor-fixo" (direto no template)
LLM / Usuário → "campo": "{{parametro}}" (definido no JSON Schema)
Client Variable → "campo": "{{client.variavel}}" (configurado nas Client Variables)
Os três tipos podem ser combinados livremente no body_template, URL, headers e query params.

