AutomationFlowsAI & RAG › WhatsApp AI Agent with OpenAI

WhatsApp AI Agent with OpenAI

Original n8n title: Whatsapp AI Agent

Whatsapp Ai Agent. Uses whatsAppTrigger, whatsApp, httpRequest, openAi. Event-driven trigger; 23 nodes.

Event trigger★★★★☆ complexityAI-powered23 nodesWhatsApp TriggerWhatsAppHTTP RequestOpenAIGoogle Sheets ToolAgentGmail ToolTool Serp Api
AI & RAG Trigger: Event Nodes: 23 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Agent → Gmail Tool recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "WhatsApp AI Agent",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "whatsapp-webhook",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "webhook-trigger",
      "name": "Webhook - Mensagem Recebida",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "filter-message-type",
              "leftValue": "={{ $json.body.data.messageType }}",
              "rightValue": "conversation",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        },
        "options": {}
      },
      "id": "filter-messages",
      "name": "Filtrar - Apenas Mensagens de Texto",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [
        460,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "filter-not-from-me",
              "leftValue": "={{ $json.body.data.key.fromMe }}",
              "rightValue": false,
              "operator": {
                "type": "boolean",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "filter-not-from-me",
      "name": "Filtrar - N\u00e3o \u00e9 do Bot",
      "type": "n8n-nodes-base.filter",
      "typeVersion": 2,
      "position": [
        660,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT phone_number FROM escalation_pauses WHERE phone_number = '{{ $json.body.data.key.remoteJid }}' AND paused_at > NOW() - INTERVAL '2 hours'",
        "options": {}
      },
      "id": "check-escalation",
      "name": "Verificar - Escala\u00e7\u00e3o Ativa?",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        860,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "not-paused",
              "leftValue": "={{ $json.length }}",
              "rightValue": 0,
              "operator": {
                "type": "number",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-not-paused",
      "name": "Se N\u00e3o Pausado",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1060,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT role, message FROM conversations WHERE phone_number = '{{ $json.body.data.key.remoteJid }}' ORDER BY created_at DESC LIMIT 20",
        "options": {}
      },
      "id": "get-history",
      "name": "Buscar Hist\u00f3rico",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1260,
        200
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversations (phone_number, role, message) VALUES ('{{ $json.body.data.key.remoteJid }}', 'user', '{{ $json.body.data.message.conversation }}')",
        "options": {}
      },
      "id": "save-user-message",
      "name": "Salvar Mensagem do Usu\u00e1rio",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        1260,
        400
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Build the LLM context with knowledge base + history\nconst knowledgeBase = $('Webhook - Mensagem Recebida').first().json;\nconst history = $('Buscar Hist\u00f3rico').all();\nconst userMessage = knowledgeBase.body.data.message.conversation;\nconst phoneNumber = knowledgeBase.body.data.key.remoteJid;\n\n// Format conversation history\nlet historyText = '';\nif (history && history.length > 0) {\n  const reversedHistory = [...history].reverse();\n  historyText = reversedHistory.map(h => {\n    const role = h.json.role === 'user' ? 'Cliente' : 'Assistente';\n    return `${role}: ${h.json.message}`;\n  }).join('\\n');\n}\n\n// Read knowledge base from file (injected via environment or static)\n// In production, this would be loaded from the knowledge_base.json\nconst systemPrompt = $env.SYSTEM_PROMPT || 'Voc\u00ea \u00e9 o assistente virtual de uma empresa de aluguel de extratoras WAP.';\n\nconst messages = [\n  {\n    role: 'user',\n    parts: [{\n      text: `${systemPrompt}\\n\\n## Hist\u00f3rico da Conversa\\n${historyText || 'Primeira mensagem do cliente.'}\\n\\n## Mensagem Atual do Cliente\\n${userMessage}\\n\\nResponda APENAS no formato JSON especificado no prompt.`\n    }]\n  }\n];\n\nreturn {\n  json: {\n    phoneNumber,\n    userMessage,\n    messages,\n    contents: messages\n  }\n};"
      },
      "id": "build-context",
      "name": "Montar Contexto LLM",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1500,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={{ $env.GEMINI_API_KEY }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ contents: $json.contents, generationConfig: { temperature: 0.7, maxOutputTokens: 1024, responseMimeType: 'application/json' } }) }}",
        "options": {
          "timeout": 30000
        }
      },
      "id": "call-gemini",
      "name": "Chamar Gemini Flash",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1720,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Parse LLM response\nconst response = $json;\nconst phoneNumber = $('Montar Contexto LLM').first().json.phoneNumber;\n\nlet parsed;\ntry {\n  const text = response.candidates[0].content.parts[0].text;\n  // Remove markdown code block if present\n  const cleaned = text.replace(/```json\\n?/g, '').replace(/```\\n?/g, '').trim();\n  parsed = JSON.parse(cleaned);\n} catch (e) {\n  parsed = {\n    resposta: response.candidates?.[0]?.content?.parts?.[0]?.text || 'Desculpe, tive um problema ao processar sua mensagem. Pode repetir?',\n    acao: null,\n    video_id: null,\n    dados_agendamento: null\n  };\n}\n\nreturn {\n  json: {\n    phoneNumber,\n    resposta: parsed.resposta,\n    acao: parsed.acao || null,\n    video_id: parsed.video_id || null,\n    dados_agendamento: parsed.dados_agendamento || null\n  }\n};"
      },
      "id": "parse-response",
      "name": "Parsear Resposta LLM",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1940,
        300
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO conversations (phone_number, role, message) VALUES ('{{ $json.phoneNumber }}', 'assistant', '{{ $json.resposta }}')",
        "options": {}
      },
      "id": "save-bot-message",
      "name": "Salvar Resposta do Bot",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2160,
        300
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.EVOLUTION_URL }}/message/sendText/{{ $env.EVOLUTION_INSTANCE }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.EVOLUTION_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ number: $json.phoneNumber.replace('@s.whatsapp.net', ''), text: $json.resposta }) }}",
        "options": {}
      },
      "id": "send-text-response",
      "name": "Enviar Resposta - Texto",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2380,
        200
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-action",
              "leftValue": "={{ $('Parsear Resposta LLM').first().json.acao }}",
              "rightValue": "enviar_video",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-send-video",
      "name": "Se Enviar V\u00eddeo",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2600,
        200
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "check-escalation-action",
              "leftValue": "={{ $('Parsear Resposta LLM').first().json.acao }}",
              "rightValue": "escalar_humano",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "if-escalate",
      "name": "Se Escalar Humano",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        2600,
        500
      ]
    },
    {
      "parameters": {
        "jsCode": "// Map video_id to actual video URL\nconst videoId = $('Parsear Resposta LLM').first().json.video_id;\nconst phoneNumber = $('Parsear Resposta LLM').first().json.phoneNumber;\n\n// Video mapping - UPDATE THESE URLs WITH YOUR ACTUAL VIDEO URLS\nconst videoMap = {\n  'uso_extratora': 'URL_DO_VIDEO_USO_EXTRATORA',\n  'montagem_equipamento': 'URL_DO_VIDEO_MONTAGEM',\n  'dicas_limpeza': 'URL_DO_VIDEO_DICAS'\n};\n\nconst videoUrl = videoMap[videoId] || null;\n\nreturn {\n  json: {\n    phoneNumber,\n    videoUrl,\n    videoId\n  }\n};"
      },
      "id": "map-video-url",
      "name": "Mapear URL do V\u00eddeo",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2820,
        100
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.EVOLUTION_URL }}/message/sendMedia/{{ $env.EVOLUTION_INSTANCE }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.EVOLUTION_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ number: $json.phoneNumber.replace('@s.whatsapp.net', ''), mediatype: 'video', media: $json.videoUrl, caption: 'Aqui est\u00e1 o v\u00eddeo tutorial! \ud83d\udcf9' }) }}",
        "options": {}
      },
      "id": "send-video",
      "name": "Enviar V\u00eddeo via Evolution",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3040,
        100
      ]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO escalation_pauses (phone_number, reason) VALUES ('{{ $('Parsear Resposta LLM').first().json.phoneNumber }}', 'AI escalou para humano') ON CONFLICT (phone_number) DO UPDATE SET paused_at = NOW(), reason = 'AI escalou para humano'",
        "options": {}
      },
      "id": "pause-bot",
      "name": "Pausar Bot para Contato",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        2820,
        500
      ],
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.EVOLUTION_URL }}/message/sendText/{{ $env.EVOLUTION_INSTANCE }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $env.EVOLUTION_API_KEY }}"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ number: $env.ATTENDANT_PHONE, text: '\ud83d\udea8 *Atendimento Humano Necess\u00e1rio*\\n\\nCliente: ' + $('Parsear Resposta LLM').first().json.phoneNumber.replace('@s.whatsapp.net', '') + '\\n\u00daltima mensagem: ' + $('Montar Contexto LLM').first().json.userMessage + '\\n\\n_Bot pausado para este contato por 2 horas._' }) }}",
        "options": {}
      },
      "id": "notify-attendant",
      "name": "Notificar Atendente",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3040,
        500
      ]
    }
  ],
  "connections": {
    "Webhook - Mensagem Recebida": {
      "main": [
        [
          {
            "node": "Filtrar - Apenas Mensagens de Texto",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filtrar - Apenas Mensagens de Texto": {
      "main": [
        [
          {
            "node": "Filtrar - N\u00e3o \u00e9 do Bot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filtrar - N\u00e3o \u00e9 do Bot": {
      "main": [
        [
          {
            "node": "Verificar - Escala\u00e7\u00e3o Ativa?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Verificar - Escala\u00e7\u00e3o Ativa?": {
      "main": [
        [
          {
            "node": "Se N\u00e3o Pausado",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Se N\u00e3o Pausado": {
      "main": [
        [
          {
            "node": "Buscar Hist\u00f3rico",
            "type": "main",
            "index": 0
          },
          {
            "node": "Salvar Mensagem do Usu\u00e1rio",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Buscar Hist\u00f3rico": {
      "main": [
        [
          {
            "node": "Montar Contexto LLM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Montar Contexto LLM": {
      "main": [
        [
          {
            "node": "Chamar Gemini Flash",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Chamar Gemini Flash": {
      "main": [
        [
          {
            "node": "Parsear Resposta LLM",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parsear Resposta LLM": {
      "main": [
        [
          {
            "node": "Salvar Resposta do Bot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Salvar Resposta do Bot": {
      "main": [
        [
          {
            "node": "Enviar Resposta - Texto",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enviar Resposta - Texto": {
      "main": [
        [
          {
            "node": "Se Enviar V\u00eddeo",
            "type": "main",
            "index": 0
          },
          {
            "node": "Se Escalar Humano",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Se Enviar V\u00eddeo": {
      "main": [
        [
          {
            "node": "Mapear URL do V\u00eddeo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Mapear URL do V\u00eddeo": {
      "main": [
        [
          {
            "node": "Enviar V\u00eddeo via Evolution",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Se Escalar Humano": {
      "main": [
        [
          {
            "node": "Pausar Bot para Contato",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pausar Bot para Contato": {
      "main": [
        [
          {
            "node": "Notificar Atendente",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1",
    "saveManualExecutions": true,
    "saveDataSuccessExecution": "all"
  },
  "staticData": null,
  "tags": [
    {
      "name": "whatsapp"
    },
    {
      "name": "ai-agent"
    },
    {
      "name": "production"
    }
  ]
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Whatsapp Ai Agent. Uses whatsAppTrigger, whatsApp, httpRequest, openAi. Event-driven trigger; 23 nodes.

Source: https://github.com/YousefAutomates/01-whatsapp-ai-agent-n8n/blob/main/workflow/whatsapp_ai_agent.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

Transform your salon/service business with this streamlined WhatsApp automation system featuring Claude integration, zero-setup database management, and intelligent conversation handling. Claude MCP I

WhatsApp Trigger, WhatsApp, Redis +11
AI & RAG

Jarvis is a powerful multi-agent productivity assistant built in n8n. It works directly from Telegram and can understand both text messages and voice notes.

Telegram Trigger, Telegram, OpenAI +10
AI & RAG

Inbox Guardian. Uses gmailTrigger, lmChatOpenAi, agent, textClassifier. Event-driven trigger; 66 nodes.

Gmail Trigger, OpenAI Chat, Agent +12
AI & RAG

Automate your personal productivity with this intelligent n8n workflow that integrates Telegram, Google Sheets, and OpenAI (GPT-4o). This system uses multiple AI agents to manage work hours, tasks, fi

Agent, OpenAI Chat, Telegram +9
AI & RAG

61- Free Secretery_MCP. Uses agent, toolThink, toolSerpApi, toolWikipedia. Event-driven trigger; 61 nodes.

Agent, Tool Think, Tool Serp Api +15