Envio de Mensagens
Esta página documenta como enviar mensagens para o agente e receber respostas via webhook.
Add Message
Envia uma mensagem para ser processada pelo agente em uma sessão ativa.
Endpoint
POST https://api.tech4.ai/agent/v2/conversation/{session_id}/message
Headers
x-client-key: YOUR_API_KEY
Content-Type: application/json
Parâmetros
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
session_id | uuid | Sim | (Path) ID da sessão ativa |
type | string | Não | Tipo de mensagem: text, audio, document, image, video, interactive. Padrão: text |
text | object | Condicional | Obrigatório quando type=text. Ver Objeto text |
audio | object | Condicional | Obrigatório quando type=audio. Ver Objeto de mídia |
document | object | Condicional | Obrigatório quando type=document. Ver Objeto de mídia |
image | object | Condicional | Obrigatório quando type=image. Ver Objeto de mídia |
video | object | Condicional | Obrigatório quando type=video. Ver Objeto de mídia |
interactive | object | Condicional | Obrigatório quando type=interactive. Ver Objeto interactive |
Objeto text
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
body | string | Sim | Conteúdo textual da mensagem |
Objeto de mídia
Aplica-se para audio, document, image e video.
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | uuid | Condicional | ID do arquivo previamente enviado via Upload File. |
caption | string | Não | Legenda ou mensagem associada ao arquivo |
Objeto interactive
Usado para representar a resposta do usuário a um componente interativo (botão ou lista). O campo type discrimina entre os dois subtipos.
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | string | Sim | Subtipo da interação: button_reply ou list_reply |
button_reply | object | Condicional | Obrigatório quando type=button_reply. Ver tabela abaixo |
list_reply | object | Condicional | Obrigatório quando type=list_reply. Ver tabela abaixo |
Campos de button_reply — resposta do usuário ao clicar em um botão de resposta rápida:
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID do botão selecionado |
title | string | Sim | Texto do botão selecionado |
Campos de list_reply — resposta do usuário ao selecionar uma opção de lista:
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id | string | Sim | ID da opção selecionada |
title | string | Sim | Título da opção selecionada |
description | string | Não | Descrição da opção selecionada |
Exemplos de Requisições
Mensagem de Texto
curl -X POST https://api.tech4.ai/agent/v2/conversation/550e8400-e29b-41d4-a716-446655440000/message \
-H "x-client-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "text",
"text": {
"body": "Olá, você pode me ajudar?"
}
}'
Mensagem de Áudio
curl -X POST https://api.tech4.ai/agent/v2/conversation/550e8400-e29b-41d4-a716-446655440000/message \
-H "x-client-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "audio",
"audio": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"caption": "Mensagem de voz do cliente"
}
}'
Mensagem com Documento
curl -X POST https://api.tech4.ai/agent/v2/conversation/550e8400-e29b-41d4-a716-446655440000/message \
-H "x-client-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "document",
"document": {
"id": "b2c3d4e5-f6a7-8901-bcde-f23456789012",
"caption": "Contrato assinado"
}
}'
Mensagem com Imagem
curl -X POST https://api.tech4.ai/agent/v2/conversation/550e8400-e29b-41d4-a716-446655440000/message \
-H "x-client-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "image",
"image": {
"id": "c3d4e5f6-a7b8-9012-cdef-345678901234",
"caption": "Foto do problema"
}
}'
Mensagem com Vídeo
curl -X POST https://api.tech4.ai/agent/v2/conversation/550e8400-e29b-41d4-a716-446655440000/message \
-H "x-client-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "video",
"video": {
"id": "d4e5f6a7-b8c9-0123-def4-567890123456",
"caption": "Demonstração do problema"
}
}'
Resposta Interativa — Botão (button_reply)
Enviada quando o usuário clica em um botão de resposta rápida exibido no Whatsapp.
curl -X POST https://api.tech4.ai/agent/v2/conversation/550e8400-e29b-41d4-a716-446655440000/message \
-H "x-client-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "interactive",
"interactive": {
"type": "button_reply",
"button_reply": {
"id": "manha",
"title": "Manhã"
}
}
}'
Resposta Interativa — Lista (list_reply)
Enviada quando o usuário seleciona uma opção de uma lista interativa exibida no Whatsapp.
curl -X POST https://api.tech4.ai/agent/v2/conversation/550e8400-e29b-41d4-a716-446655440000/message \
-H "x-client-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "interactive",
"interactive": {
"type": "list_reply",
"list_reply": {
"id": "pro_anual",
"title": "Pro Anual",
"description": "R$ 79,90/mês"
}
}
}'
Respostas da API
Sucesso (202 Accepted)
Mensagem aceita para processamento.
{
"status": "processing",
"message_id": "d4e5f6a7-b8c9-0123-def4-56789012345",
"detail": "message sent successfully"
}
Requisição Inválida (400 Bad Request)
A sessão está fechada.
{
"status": "session_closed",
"detail": "Session is closed"
}
Não Encontrado (404 Not Found)
A sessão não existe.
{
"detail": "Session not found"
}
Conflito (409 Conflict)
Mensagem duplicada detectada.
{
"detail": "Message duplicate detected"
}
Webhook Response
Após processar a mensagem, o agente envia a resposta para o webhook configurado na abertura da conversa.
Estrutura do Webhook
| Campo | Tipo | Descrição |
|---|---|---|
response | object | Resposta do agente com role ("assistant") e content (array de conteúdos ver Tipos de item em response.content) |
relation_message_ids | array | Lista de UUIDs das mensagens consideradas no processamento |
session_id | uuid | ID da sessão |
metadata | object | Metadados opcionais (ver Objeto metadata) |
status | string | Status do processo: done ou fail |
detail | string | Mensagem detalhando o resultado |
Headers HTTP do Webhook
| Header | Descrição |
|---|---|
X-Tech4AI-Signature-256 | Assinatura HMAC-SHA256 do body no formato sha256=<hex>. Ver Verificação de Assinatura. |
Objeto response.content
O array response.content pode conter itens de diferentes tipos numa mesma resposta. Cada item possui um campo type que indica o formato:
type | Descrição |
|---|---|
text | Mensagem de texto simples. Contém campo text.body (string). |
document | Arquivo enviado pelo agente. Contém campo document com id, url, filename e expiration_time. |
interactive | Componente interativo (botões ou lista). O subcampo interactive.type pode ser "button" (reply buttons) ou "list" (lista interativa). |
Parâmetros do item text
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | string | Sim | Sempre "text". |
text | object | Sim | Objeto com o conteúdo textual. |
text.body | string | Sim | Texto da mensagem. |
Exemplo:
{
"type": "text",
"text": {
"body": "Olá! Como posso te ajudar hoje?"
}
}
Parâmetros do item interactive — Reply Buttons
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | string | Sim | Sempre "interactive". |
interactive.type | string | Sim | Sempre "button" para reply buttons. |
interactive.body | object | Sim | Objeto com campo text (string, ≤ 1024 chars). Texto principal exibido acima dos botões. |
interactive.body.text | string | Sim | Conteúdo textual do corpo. |
interactive.action | object | Sim | Objeto com campo buttons — lista de 1 a 3 botões. |
interactive.action.buttons[] | array | Sim | Lista de reply buttons. Máximo 3 itens. |
interactive.action.buttons[].type | string | Sim | Sempre "reply". |
interactive.action.buttons[].reply | object | Sim | Dados do botão. |
interactive.action.buttons[].reply.id | string | Sim | Identificador único do botão. Máximo 256 chars. |
interactive.action.buttons[].reply.title | string | Sim | Texto exibido no botão. Máximo 20 chars. |
interactive.header | object | Não | Cabeçalho opcional. Ver tabela de tipos de header abaixo. |
interactive.footer | object | Não | Rodapé opcional. |
interactive.footer.text | string | Não | Texto do rodapé. Máximo 60 chars. |
Tipos de interactive.header para reply buttons:
header.type | Campos adicionais | Descrição |
|---|---|---|
"text" | text (string, ≤ 60 chars) | Cabeçalho de texto simples. |
"image" | image.link (string) | URL pública da imagem. |
"document" | document.link (string) | URL pública do documento. |
"video" | video.link (string) | URL pública do vídeo. |
Exemplo de item interactive com reply buttons:
{
"type": "interactive",
"interactive": {
"type": "button",
"header": {
"type": "text",
"text": "Agendamento"
},
"body": {
"text": "Qual período você prefere para o atendimento?"
},
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "manha", "title": "Manhã" } },
{ "type": "reply", "reply": { "id": "tarde", "title": "Tarde" } },
{ "type": "reply", "reply": { "id": "noite", "title": "Noite" } }
]
},
"footer": {
"text": "Seg a Sex, das 8h às 20h"
}
}
}
Parâmetros do item interactive — Lista Interativa
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
type | string | Sim | Sempre "interactive". |
interactive.type | string | Sim | Sempre "list" para listas interativas. |
interactive.body | object | Sim | Objeto com campo text (string, ≤ 4096 chars). Texto principal exibido acima da lista. |
interactive.body.text | string | Sim | Conteúdo textual do corpo. |
interactive.action | object | Sim | Objeto com button e sections. |
interactive.action.button | string | Sim | Label do botão que abre a lista. Máximo 20 chars. |
interactive.action.sections[] | array | Sim | Lista de 1 a 10 seções. Total de rows entre todas as seções: máximo 10. |
interactive.action.sections[].title | string | Sim | Título da seção. Máximo 24 chars. |
interactive.action.sections[].rows[] | array | Sim | Opções da seção. Mínimo 1 item por seção. |
interactive.action.sections[].rows[].id | string | Sim | Identificador único da opção. Máximo 256 chars. |
interactive.action.sections[].rows[].title | string | Sim | Texto da opção exibido na lista. Máximo 24 chars. |
interactive.action.sections[].rows[].description | string | Não | Descrição adicional da opção. Máximo 72 chars. |
interactive.header | object | Não | Cabeçalho opcional. Apenas type: "text" é suportado pelo WhatsApp para listas. |
interactive.header.type | string | Não | Sempre "text" quando presente. |
interactive.header.text | string | Não | Texto do cabeçalho. Máximo 60 chars. |
interactive.footer | object | Não | Rodapé opcional. |
interactive.footer.text | string | Não | Texto do rodapé. Máximo 60 chars. |
Exemplo de item interactive com lista:
{
"type": "interactive",
"interactive": {
"type": "list",
"header": {
"type": "text",
"text": "Planos disponíveis"
},
"body": {
"text": "Selecione o plano de sua preferência"
},
"action": {
"button": "Ver planos",
"sections": [
{
"title": "Planos Mensais",
"rows": [
{ "id": "basico", "title": "Básico", "description": "R$ 49,90/mês" },
{ "id": "pro", "title": "Pro", "description": "R$ 99,90/mês" }
]
},
{
"title": "Planos Anuais",
"rows": [
{ "id": "basico_anual", "title": "Básico Anual", "description": "R$ 39,90/mês" },
{ "id": "pro_anual", "title": "Pro Anual", "description": "R$ 79,90/mês" }
]
}
]
},
"footer": {
"text": "Valores promocionais por tempo limitado"
}
}
}
Quando o usuário responde a um componente interativo, o sistema popula automaticamente as chaves last_interactive e interactive_history no state da sessão, acessíveis em qualquer action subsequente.
Consulte a documentação completa em State Management — Componentes Interativos.
Objeto metadata
| Campo | Tipo | Descrição |
|---|---|---|
functions | array | Lista de funções solicitadas (ver Funções especiais) |
session_info | object | Dados coletados durante a sessão (ex: informações de contato) |
Funções Especiais
O agente pode solicitar a execução de funções especiais através do campo metadata.functions. Para mais detalhes, consulte a documentação do Response Objects.
Transferência de Habilidade Externa
Quando o agente identifica a necessidade de transbordo para uma habilidade externa:
{
"name": "external_skill_transfer",
"parameters": {
"external_skill_name": "pagamentos",
"external_skill_id": "96db1376-1ce6-4dc7-a09d-e04e68f6bed2",
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"reason": "o cliente deseja efetuar o pagamento de um boleto pendente"
}
}
Encerramento de Conversa
Quando o agente decide finalizar a conversa:
{
"name": "end_chat",
"parameters": {
"reason": "Cliente pagou o boleto e agradeceu.",
"service_evaluation": true
}
}
service_evaluation (bool, default true) controla se o servidor consumidor deve disparar a pesquisa de satisfação (CSAT) ao final do atendimento. Quando omitido ou true, o comportamento é o mesmo de sempre (CSAT disparado). Quando false — ex. em um retorno de HSM informativo — o servidor deve pular o disparo do CSAT.
Exemplos de Webhook
Webhook - Sucesso
{
"response": {
"role": "assistant",
"content": [
{
"type": "text",
"text": {
"body": "Sua fatura do mês é:"
}
},
{
"type": "document",
"document": {
"id": "4c80b53a-a667-4edd-b943-d1e9dc387009",
"url": "https://storage.example.com/files/fatura.pdf",
"expiration_time": "2025-09-26T20:53:48.368151+00:00",
"filename": "fatura_outubro.pdf"
}
}
]
},
"relation_message_ids": [
"b15c35a4-da85-4d58-842c-cff5a1f0b45a",
"4ab9a72d-5497-4a76-8bbf-a0025c6ad09f"
],
"session_id": "8d1a4259-c4d0-45b8-845b-b290c0264754",
"metadata": {
"functions": [
{
"name": "external_skill_transfer",
"parameters": {
"external_skill_name": "pagamentos",
"external_skill_id": "96db1376-1ce6-4dc7-a09d-e04e68f6bed2",
"user_id": "123e4567-e89b-12d3-a456-426614174000",
"reason": "o cliente deseja efetuar o pagamento de um boleto pendente"
}
}
],
"session_info": {
"contact": {
"name": "João Silva",
"cpf": "123.456.789-00"
}
}
},
"status": "done",
"detail": "response generated successfully"
}
Webhook - Falha
{
"relation_message_ids": [
"b15c35a4-da85-4d58-842c-cff5a1f0b45a",
"4ab9a72d-5497-4a76-8bbf-a0025c6ad09f"
],
"session_id": "8d1a4259-c4d0-45b8-845b-b290c0264754",
"metadata": null,
"status": "fail",
"detail": "fail to generate response"
}
Webhook - Resposta com Reply Buttons
Quando o agente envia botões de resposta rápida, response.content contém um item type: "interactive" com interactive.type: "button".
{
"response": {
"role": "assistant",
"content": [
{
"type": "interactive",
"interactive": {
"type": "button",
"body": {
"text": "Qual período você prefere para o atendimento?"
},
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "manha", "title": "Manhã" } },
{ "type": "reply", "reply": { "id": "tarde", "title": "Tarde" } },
{ "type": "reply", "reply": { "id": "noite", "title": "Noite" } }
]
},
"footer": {
"text": "Seg a Sex, das 8h às 20h"
}
}
}
]
},
"relation_message_ids": ["b15c35a4-da85-4d58-842c-cff5a1f0b45a"],
"session_id": "8d1a4259-c4d0-45b8-845b-b290c0264754",
"metadata": {},
"status": "done",
"detail": "response generated successfully"
}
Webhook - Resposta com Lista Interativa
Quando o agente envia uma lista de opções, response.content contém um item type: "interactive" com interactive.type: "list".
{
"response": {
"role": "assistant",
"content": [
{
"type": "interactive",
"interactive": {
"type": "list",
"header": {
"type": "text",
"text": "Planos disponíveis"
},
"body": {
"text": "Selecione o plano de sua preferência"
},
"action": {
"button": "Ver planos",
"sections": [
{
"title": "Planos Mensais",
"rows": [
{ "id": "basico", "title": "Básico", "description": "R$ 49,90/mês" },
{ "id": "pro", "title": "Pro", "description": "R$ 99,90/mês" }
]
},
{
"title": "Planos Anuais",
"rows": [
{ "id": "basico_anual", "title": "Básico Anual", "description": "R$ 39,90/mês" },
{ "id": "pro_anual", "title": "Pro Anual", "description": "R$ 79,90/mês" }
]
}
]
},
"footer": {
"text": "Valores promocionais por tempo limitado"
}
}
}
]
},
"relation_message_ids": ["c26d46b5-eb96-5e69-953d-dg6012g1456b"],
"session_id": "8d1a4259-c4d0-45b8-845b-b290c0264754",
"metadata": {},
"status": "done",
"detail": "response generated successfully"
}
Webhook - Resposta Mista (Texto + Componente Interativo)
response.content pode conter múltiplos itens de tipos diferentes na mesma resposta. Cada item deve ser renderizado sequencialmente.
{
"response": {
"role": "assistant",
"content": [
{
"type": "text",
"text": { "body": "Antes de continuar, escolha uma opção:" }
},
{
"type": "interactive",
"interactive": {
"type": "button",
"body": { "text": "Como deseja prosseguir?" },
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "agendar", "title": "Agendar" } },
{ "type": "reply", "reply": { "id": "planos", "title": "Ver planos" } }
]
}
}
}
]
},
"relation_message_ids": ["d37e57c6-fc07-6f7a-064e-eh7123h2567c"],
"session_id": "8d1a4259-c4d0-45b8-845b-b290c0264754",
"metadata": {},
"status": "done",
"detail": "response generated successfully"
}
Verificação de Assinatura
Toda requisição de webhook inclui o header X-Tech4AI-Signature-256 com a assinatura HMAC-SHA256 do body. Use essa assinatura para confirmar que a requisição foi enviada pela Tech4AI e que o conteúdo não foi alterado em trânsito.
Como funciona
- O body do webhook é serializado de forma determinística (chaves ordenadas alfabeticamente, sem espaços)
- O HMAC-SHA256 é calculado sobre esse body usando o
x-client-keycomo secret - O resultado é enviado no header no formato
sha256=<hex_digest>
Como validar
Recalcule a assinatura no seu servidor e compare com o header recebido usando comparação segura contra timing attacks.
- Python
- Node.js
import hashlib
import hmac
import json
def verify_webhook_signature(
body: bytes,
secret: str,
received_signature: str,
) -> bool:
"""
Valida o header X-Tech4AI-Signature-256 do webhook.
Args:
body: Body HTTP cru recebido (bytes)
secret: x-client-key usado no add_message
received_signature: valor do header X-Tech4AI-Signature-256
Returns:
True se a assinatura for válida
"""
payload = json.loads(body)
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
expected = "sha256=" + hmac.new(
secret.encode("utf-8"),
canonical.encode("utf-8"),
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, received_signature)
const crypto = require("crypto");
/**
* Ordena recursivamente as chaves de um objeto (equivalente ao sort_keys=True do Python).
*/
function sortKeysRecursively(obj) {
if (Array.isArray(obj)) return obj.map(sortKeysRecursively);
if (obj !== null && typeof obj === "object") {
return Object.keys(obj)
.sort()
.reduce((acc, key) => {
acc[key] = sortKeysRecursively(obj[key]);
return acc;
}, {});
}
return obj;
}
/**
* Escapa caracteres não-ASCII como o json.dumps do Python faz por padrão.
*/
function escapeNonAscii(str) {
return str.replace(/[^\x00-\x7F]/g, (char) =>
"\\u" + char.charCodeAt(0).toString(16).padStart(4, "0")
);
}
/**
* Valida o header X-Tech4AI-Signature-256 do webhook.
*/
function verifyWebhookSignature(body, secret, receivedSignature) {
const payload = JSON.parse(body);
const canonical = escapeNonAscii(JSON.stringify(sortKeysRecursively(payload)));
const expected =
"sha256=" +
crypto.createHmac("sha256", secret).update(canonical).digest("hex");
const expectedBuffer = Buffer.from(expected);
const receivedBuffer = Buffer.from(receivedSignature || "");
if (expectedBuffer.length !== receivedBuffer.length) return false;
return crypto.timingSafeEqual(expectedBuffer, receivedBuffer);
}
- Use sempre comparação segura (
hmac.compare_digestem Python,crypto.timingSafeEqualem Node.js) para evitar timing attacks - O header
X-Tech4AI-Signature-256está sempre presente nas requisições de webhook - Rejeite requisições com assinatura inválida retornando HTTP
401
Boas Práticas
- Valide o webhook: Certifique-se de que seu webhook retorna status 200
- Valide a assinatura: Verifique o header
X-Tech4AI-Signature-256para garantir que o webhook veio da Tech4AI (ver Verificação de Assinatura) - Armazene message_id: Use para rastreamento e debugging
- Use caption: Adicione contexto aos arquivos de mídia
- Monitore relation_message_ids: Entenda quais mensagens foram processadas juntas
- Arquivos enviados por URL devem estar acessíveis publicamente
- URLs de download de arquivos gerados pelo agente expiram após o tempo indicado em
expiration_time - Seu webhook deve responder em até 30 segundos
Próximos Passos
Explore mais sobre: