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 →
{
"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.
gmailOAuth2googleSheetsOAuth2ApihttpBearerAuthserpApiwhatsAppApiwhatsAppTriggerApi
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 →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
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
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.
Inbox Guardian. Uses gmailTrigger, lmChatOpenAi, agent, textClassifier. Event-driven trigger; 66 nodes.
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
61- Free Secretery_MCP. Uses agent, toolThink, toolSerpApi, toolWikipedia. Event-driven trigger; 61 nodes.