fix(n8n): responde de forma sincrona (texto puro) e usa memoria simples

O Z-PRO captura a resposta HTTP do webhook e reenvia direto ao cliente
(variavel n8n1 -> passo Mensagem), entao o envio assincrono via API duplicava
a mensagem. Troca para responseMode: responseNode + Respond to Webhook em
texto puro, remove o node de envio assincrono, e troca a memoria de sessao
de Postgres (reservado para outro fluxo) para o node Simple Memory nativo.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Eric
2026-08-21 09:13:27 -03:00
co-authored by Claude Sonnet 5
parent 772747d74e
commit 314ccf5cdf
2 changed files with 160 additions and 185 deletions
@@ -205,11 +205,62 @@ agendamento desta conta (`Agendamento - GChat - Z-PRO (clínica exemplo)`):
Os dois testes reais feitos pelo usuário mostraram o webhook recebendo
`body: {}` (content-length 0, user-agent `axios/1.19.0`) — ou seja, o Z-PRO
não mandou `protocol`/`entrada`/`contactName`/`number` como os bots de
agendamento recebem. Isso sugere que a URL foi configurada num campo de
webhook diferente do usado pelo ChatFlow desses bots (que é o que gera esse
formato JSON). Ainda em aberto — precisa confirmar com o usuário onde
exatamente a URL foi cadastrada no painel do Z-PRO, e ajustar pro mesmo
mecanismo usado pelos bots que já funcionam (ChatFlow → passo "Webhook").
agendamento recebem. Causa raiz confirmada pelo usuário: no ChatFlow do
Z-PRO, o passo "Webhook Avançado" do fluxo de orçamento (`Menu Orçamento` /
`Menu Orçamento 2`) estava com o campo **Body** vazio, enquanto o do fluxo de
agendamento (`Menu Agendamento` / `Menu Agendamento 2`, que já funciona) tem:
```json
{
"step": "process",
"entrada": "{{entrada}}",
"number": "{{phoneNumber}}",
"contactName": "{{name}}",
"protocol": "{{protocol}}"
}
```
Ação do usuário: copiar o mesmo Body (com essas mesmas variáveis) para os
passos "Webhook Avançado" de `Menu Orçamento` e `Menu Orçamento 2`. Os nomes
de campo já batem com o que o node "Extrair campos do webhook" espera
(`body.protocol`, `body.number`, `body.contactName`, `body.entrada`) — não
precisou ajustar nada do lado do n8n para esses campos.
### Descoberta 2: o Z-PRO captura a resposta HTTP e reenvia ao cliente
Nas telas do ChatFlow, o passo "Webhook Avançado" tem uma seção "Resposta da
API (captura e roteamento)": ele salva o corpo da resposta HTTP do nosso
webhook numa variável (`n8n1`) e o passo seguinte ("Mensagem") manda
`{{n8n1}}` de volta ao cliente pelo WhatsApp. Ou seja, **o Z-PRO não usa a
API de envio de mensagens para essa resposta — ele repassa direto o que o
webhook devolver na hora**. Como o "Caminho no JSON" fica vazio, o corpo
precisa ser **texto puro**, não JSON.
Isso invalida a decisão original de "envio assíncrono via API do Z-PRO": o
workflow foi ajustado para responder de forma **síncrona**, com
`responseMode: responseNode` no Webhook e um node "Respond to Webhook"
(`respondWith: text`) devolvendo o texto final direto na resposta HTTP. O
node "Enviar orçamento (Z-PRO)" (chamada assíncrona ao sub-workflow
`enviar_texto`) foi removido — mantê-lo mandaria a mensagem duas vezes.
### Descoberta 3: memória de conversa
A credencial Postgres reaproveitada do bot de agendamento
(`Postgres Agendamento - (clínica exemplo)`) é de uso exclusivo daquele
fluxo — o usuário pediu para não usá-la aqui sem autorização explícita. A
memória de sessão agora usa o node nativo **Simple Memory**
(`memoryBufferWindow`, sem credencial), com `sessionKey` prefixado
(`orcamento-<protocolo ou telefone>`) para isolar as conversas de orçamento
das de agendamento.
### Ainda em aberto
Não há, até agora, nenhuma variável de mídia/imagem confirmada no seletor de
variáveis do ChatFlow do Z-PRO (o Body de `Menu Agendamento` só tem campos de
texto). Falta o usuário verificar, ao editar o Body do passo "Webhook
Avançado", se existe alguma variável de imagem/anexo no seletor (ícone "×")
— se existir, incluir no Body (ex.: `"mediaUrl": "{{variavel}}"`) e informar
o nome exato pra ajustar o node "Extrair campos do webhook".
## Fora de escopo (por enquanto)
+104 -180
View File
@@ -4,32 +4,24 @@
"active": true,
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "orcamento-exames-zpro",
"options": {},
"responseMode": "responseNode"
},
"id": "webhook-orcamento",
"name": "Webhook - Foto Exame",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2.1,
"position": [
-600,
-608,
0
],
"parameters": {
"httpMethod": "POST",
"path": "orcamento-exames-zpro",
"responseMode": "onReceived",
"options": {}
},
"webhookId": "5ea5f89e-7351-4d45-a881-0055009423a0",
"onError": "continueRegularOutput"
},
{
"id": "extrair-campos",
"name": "Extrair campos do webhook",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-360,
0
],
"parameters": {
"assignments": {
"assignments": [
@@ -72,24 +64,24 @@
]
},
"options": {}
}
},
"id": "extrair-campos",
"name": "Extrair campos do webhook",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
-368,
0
]
},
{
"id": "if-e-imagem",
"name": "É mensagem de imagem?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-120,
0
],
"parameters": {
"conditions": {
"options": {
"version": 2,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
"leftValue": "",
"typeValidation": "strict",
"version": 2
},
"combinator": "and",
"conditions": [
@@ -106,19 +98,18 @@
]
},
"options": {}
}
},
"id": "if-e-imagem",
"name": "É mensagem de imagem?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.3,
"position": [
-128,
0
]
},
{
"id": "baixar-imagem",
"name": "Baixar imagem",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
120,
-120
],
"parameters": {
"method": "GET",
"url": "={{ $json.imagemUrl }}",
"options": {
"response": {
@@ -129,24 +120,33 @@
"timeout": 20000
}
},
"id": "baixar-imagem",
"name": "Baixar imagem",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.4,
"position": [
64,
-288
],
"onError": "continueErrorOutput"
},
{
"id": "openai-chat-model-orcamento",
"name": "OpenAI Chat Model - Orçamento",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.3,
"position": [
360,
-280
],
"parameters": {
"model": {
"mode": "id",
"value": "gpt-4o"
},
"builtInTools": {},
"options": {}
},
"id": "openai-chat-model-orcamento",
"name": "OpenAI Chat Model - Orçamento",
"type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
"typeVersion": 1.3,
"position": [
96,
128
],
"credentials": {
"openAiApi": {
"id": "h16jFm10cTU15k6M",
@@ -155,14 +155,6 @@
}
},
{
"id": "ai-agent-orcamento",
"name": "AI Agent - Orçamento",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
360,
-120
],
"parameters": {
"promptType": "define",
"text": "={{ $('Extrair campos do webhook').item.json.entrada || 'O cliente enviou uma imagem (sem texto). Veja a imagem anexada, que é o pedido médico dele.' }}",
@@ -171,136 +163,67 @@
"passthroughBinaryImages": true
}
},
"id": "ai-agent-orcamento",
"name": "AI Agent - Orçamento",
"type": "@n8n/n8n-nodes-langchain.agent",
"typeVersion": 3.1,
"position": [
160,
-96
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"assignments": {
"assignments": [
{
"id": "resp-output",
"name": "output",
"type": "string",
"value": "={{ $json.output ? $json.output : 'Recebemos sua solicitação de exames! Nossa equipe vai confirmar os valores e te retornar em breve.' }}"
}
]
},
"options": {}
},
"id": "montar-corpo-resposta",
"name": "Montar corpo da resposta",
"type": "n8n-nodes-base.set",
"typeVersion": 3.4,
"position": [
640,
-120
],
"parameters": {
"assignments": {
"assignments": [
{
"id": "resp-number",
"name": "number",
"type": "string",
"value": "={{ $('Extrair campos do webhook').item.json.telefone }}"
},
{
"id": "resp-body",
"name": "body",
"type": "string",
"value": "={{ $json.output ? $json.output : 'Recebemos sua solicitação de exames! Nossa equipe vai confirmar os valores e te retornar em breve.' }}"
},
{
"id": "resp-externalkey",
"name": "externalKey",
"type": "string",
"value": "={{ 'orcamento-' + $execution.id }}"
},
{
"id": "resp-isclosed",
"name": "isClosed",
"type": "boolean",
"value": false
}
]
},
"options": {}
}
-128
]
},
{
"id": "enviar-orcamento-zpro",
"name": "Enviar orçamento (Z-PRO)",
"type": "n8n-nodes-base.executeWorkflow",
"typeVersion": 1.3,
"position": [
900,
-120
],
"parameters": {
"workflowId": {
"__rl": true,
"mode": "id",
"value": "Y4ndG7GuIUrmVRxV",
"cachedResultName": "MCP Server - Mensageria ZPRO - enviar_texto"
},
"workflowInputs": {
"mappingMode": "defineBelow",
"value": {
"number": "={{ $json.number }}",
"body": "={{ $json.body }}",
"externalKey": "={{ $json.externalKey }}",
"isClosed": "={{ $json.isClosed }}"
},
"matchingColumns": [],
"schema": [
{
"id": "number",
"displayName": "number",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
},
{
"id": "body",
"displayName": "body",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
},
{
"id": "externalKey",
"displayName": "externalKey",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "string"
},
{
"id": "isClosed",
"displayName": "isClosed",
"required": false,
"defaultMatch": false,
"display": true,
"canBeUsedToMatch": true,
"type": "boolean"
}
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
}
}
},
{
"id": "memoria-sessao-orcamento",
"name": "Memória da Sessão (Postgres)",
"type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
"typeVersion": 1.4,
"position": [
360,
40
],
"parameters": {
"sessionIdType": "customKey",
"sessionKey": "={{ 'orcamento-' + ($('Extrair campos do webhook').item.json.sessionId || $('Extrair campos do webhook').item.json.telefone) }}",
"contextWindowLength": 20
},
"credentials": {
"postgres": {
"id": "xq3QAN3MZSOCVgdY",
"name": "Postgres Agendamento - (clínica exemplo)"
}
"type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
"typeVersion": 1.4,
"position": [
240,
112
],
"id": "aa58f6fd-5d98-43ed-b645-eeb7e3494564",
"name": "Simple Memory"
},
{
"id": "responder-webhook",
"name": "Responder Webhook (texto)",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1.5,
"position": [
900,
-128
],
"parameters": {
"respondWith": "text",
"responseBody": "={{ $json.output }}",
"options": {}
}
}
],
@@ -392,18 +315,7 @@
]
]
},
"Montar corpo da resposta": {
"main": [
[
{
"node": "Enviar orçamento (Z-PRO)",
"type": "main",
"index": 0
}
]
]
},
"Memória da Sessão (Postgres)": {
"Simple Memory": {
"ai_memory": [
[
{
@@ -413,6 +325,17 @@
}
]
]
},
"Montar corpo da resposta": {
"main": [
[
{
"node": "Responder Webhook (texto)",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
@@ -420,6 +343,7 @@
"saveDataErrorExecution": "all",
"saveDataSuccessExecution": "all",
"saveManualExecutions": true,
"saveExecutionProgress": true
"saveExecutionProgress": true,
"binaryMode": "separate"
}
}