This workflow follows the HTTP Request → Postgres 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": "FerreBot \u2014 Agente Ventas Ferreter\u00eda WhatsApp",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "whatsapp-ferreteria",
"responseMode": "responseNode",
"options": {}
},
"id": "d6b7a8ae-83d3-4dfd-8b69-36c7a2a6a91f",
"name": "WhatsApp Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
180,
300
]
},
{
"parameters": {
"language": "javaScript",
"jsCode": "const body = $input.first().json;\nlet messageText = '';\nlet from = '';\nlet messageId = '';\nlet wabaId = '';\nif (body?.entry?.[0]?.changes?.[0]?.value?.messages) {\n const msg = body.entry[0].changes[0].value.messages[0];\n if (!['text','interactive','button'].includes(msg.type)) {\n return [{ json: { skip: true, reason: 'non-text message' } }];\n }\n messageText = msg.text?.body || msg.interactive?.button_reply?.title || msg.interactive?.list_reply?.title || msg.button?.text || '';\n from = msg.from;\n messageId = msg.id;\n wabaId = body.entry[0].changes[0].value.metadata?.phone_number_id;\n} else if (body?.['hub.challenge']) {\n return [{ json: { skip: true, challenge: body['hub.challenge'] } }];\n} else {\n return [{ json: { skip: true, reason: 'unknown format' } }];\n}\nif (!messageText.trim() || !from) {\n return [{ json: { skip: true, reason: 'empty message' } }];\n}\nreturn [{ json: { skip: false, messageText: messageText.trim(), from, messageId, wabaId, timestamp: new Date().toISOString() } }];"
},
"id": "7cbc0b7b-c526-40f5-9647-3636d9ba73e2",
"name": "Parsear Mensaje",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
400,
300
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond-skip",
"leftValue": "={{ $json.skip }}",
"rightValue": false,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "bcb0e952-2472-4362-ac67-4e2810eb7dd6",
"name": "\u00bfMensaje V\u00e1lido?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
620,
300
]
},
{
"parameters": {
"respondWith": "text",
"responseBody": "OK",
"options": {
"responseCode": 200
}
},
"id": "37e5ea27-0517-4b2b-91d7-f76e9ee327df",
"name": "Responder 200 OK",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
840,
460
]
},
{
"parameters": {
"operation": "get",
"key": "={{ 'ferrebot_session_' + $json.from }}",
"options": {}
},
"id": "ef38d860-182d-42c2-bc05-155f05d6bae0",
"name": "Obtener Sesi\u00f3n Redis",
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
840,
300
],
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"language": "javaScript",
"jsCode": "const sessionRaw = $input.first().json?.value ?? null;\nconst msg = $('Parsear Mensaje').first().json;\nlet session;\ntry { session = sessionRaw ? JSON.parse(sessionRaw) : null; } catch (e) { session = null; }\nif (!session) {\n session = { from: msg.from, state: 'inicio', cart: [], customerName: '', address: '', paymentMethod: '', orderId: '', history: [], createdAt: new Date().toISOString() };\n}\nsession.history = session.history || [];\nsession.history.push({ role: 'user', content: msg.messageText, ts: new Date().toISOString() });\nif (session.history.length > 20) { session.history = session.history.slice(-20); }\nsession.lastMessage = msg.messageText;\nsession.lastActivity = new Date().toISOString();\nreturn [{ json: { session, messageText: msg.messageText, from: msg.from, wabaId: msg.wabaId, messageId: msg.messageId } }];"
},
"id": "93d9b699-e3eb-4e00-972e-f5730c80e7ed",
"name": "Construir Sesi\u00f3n",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1060,
300
]
},
{
"parameters": {
"language": "javaScript",
"jsCode": "const { session } = $input.first().json;\nconst systemPrompt = `Eres FerreBot, agente de ventas de Ferreter\u00eda El Constructor.\n\n=== CAT\u00c1LOGO ===\n[{\"id\":\"H001\",\"nombre\":\"Martillo Carpintero 16oz\",\"precio\":45000},{\"id\":\"H002\",\"nombre\":\"Destornillador Estrella #2\",\"precio\":12000},{\"id\":\"H003\",\"nombre\":\"Taladro Inal\u00e1mbrico 18V\",\"precio\":280000},{\"id\":\"H004\",\"nombre\":\"Nivel Aluminio 60cm\",\"precio\":35000},{\"id\":\"H005\",\"nombre\":\"Llave Inglesa 12 pulgadas\",\"precio\":28000},{\"id\":\"M001\",\"nombre\":\"Cemento Gris 50kg\",\"precio\":32000},{\"id\":\"M002\",\"nombre\":\"Arena de R\u00edo bulto 40kg\",\"precio\":8500},{\"id\":\"M003\",\"nombre\":\"Varilla Corrugada 3/8\",\"precio\":18000},{\"id\":\"M004\",\"nombre\":\"Ladrillo Tolete unidad\",\"precio\":800},{\"id\":\"E001\",\"nombre\":\"Cable THW #12 rollo 100m\",\"precio\":95000},{\"id\":\"E002\",\"nombre\":\"Toma Corriente Doble\",\"precio\":8500},{\"id\":\"E003\",\"nombre\":\"Interruptor Sencillo\",\"precio\":6500},{\"id\":\"P001\",\"nombre\":\"Tubo PVC 1/2 x 6m\",\"precio\":12000},{\"id\":\"P002\",\"nombre\":\"Codo PVC 1/2\",\"precio\":900},{\"id\":\"P003\",\"nombre\":\"Llave de Paso 1/2\",\"precio\":18500},{\"id\":\"F001\",\"nombre\":\"Pintura Viniltex Blanco 1/4 GL\",\"precio\":22000},{\"id\":\"F002\",\"nombre\":\"Brocha 3 pulgadas\",\"precio\":7500},{\"id\":\"F003\",\"nombre\":\"Rodillo Felpa 9 pulgadas\",\"precio\":14000}]\n\n=== POL\u00cdTICAS ===\n- Horario: Lun-S\u00e1b 7am-6pm\n- Domicilio GRATIS >= $150.000; menor costo $15.000\n- Pago: Efectivo, Transferencia, Nequi, Daviplata\n- Entrega: 2-4 horas\n\n=== CARRITO ACTUAL ===\n${JSON.stringify(session.cart)}\nSubtotal: $${(session.subtotal||0).toLocaleString('es-CO')} | Domicilio: $${(session.domicilio||0).toLocaleString('es-CO')} | TOTAL: $${(session.total||0).toLocaleString('es-CO')}\n\n=== SESI\u00d3N ===\nEstado: ${session.state} | Cliente: ${session.customerName||'?'} | Direcci\u00f3n: ${session.address||'?'} | Pago: ${session.paymentMethod||'?'} | Orden: ${session.orderId||'pendiente'}\n\n=== FLUJO ===\ninicio \u2192 catalogo \u2192 carrito \u2192 datos \u2192 pago \u2192 confirmacion \u2192 finalizado\n\n=== REGLAS ===\n1. Responde en espa\u00f1ol colombiano con emojis \ud83d\udd28\ud83e\ude9b\ud83d\udd27\u26a1\ud83c\udfd7\ufe0f\ud83d\uded2\n2. Agregar producto: [CART_ACTION:add|ID|CANTIDAD]\n3. Quitar producto: [CART_ACTION:remove|ID|0]\n4. Cambiar estado: [STATE:estado]\n5. Pedido listo para DB: [ORDER_READY]\n6. N\u00famero de orden: [ORDER_ID:FEC-${Math.floor(10000+Math.random()*89999)}]\n7. Nombre cliente: [CUSTOMER_NAME:nombre]\n8. Direcci\u00f3n: [ADDRESS:direcci\u00f3n]\n9. Pago: [PAYMENT:m\u00e9todo]\n10. Solo productos del cat\u00e1logo. S\u00e9 proactivo y sugiere complementos.`;\nconst historyMessages = (session.history||[]).slice(-10).map(h=>({role:h.role,content:h.content}));\nconst messages = [{role:'system',content:systemPrompt},...historyMessages];\nreturn [{ json: { messages, session, from: $input.first().json.from, wabaId: $input.first().json.wabaId } }];"
},
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "Preparar Prompt",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1280,
300
]
},
{
"parameters": {
"method": "POST",
"url": "https://api.openai.com/v1/chat/completions",
"authentication": "predefinedCredentialType",
"nodeCredentialType": "openAiApi",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"model\": \"gpt-4o\",\n \"temperature\": 0.6,\n \"max_tokens\": 600,\n \"messages\": {{ JSON.stringify($json.messages) }}\n}",
"options": {
"timeout": 30000
}
},
"id": "5be5bc4c-dca7-4849-b8ae-e5dc240b49a9",
"name": "OpenAI GPT-4o",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
1500,
300
],
"credentials": {
"openAiApi": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"language": "javaScript",
"jsCode": "const openAiResp = $input.first().json;\nconst prevNode = $('Preparar Prompt').first().json;\nconst session = prevNode.session;\nconst rawResponse = openAiResp.choices?.[0]?.message?.content || '';\nif (!rawResponse) { return [{ json: { reply: 'Lo siento, tuve un problema t\u00e9cnico. Intenta de nuevo \ud83d\ude4f', session, orderReady: false, from: prevNode.from, wabaId: prevNode.wabaId } }]; }\nlet reply = rawResponse;\nlet newState = session.state;\nlet orderId = session.orderId;\nlet customerName = session.customerName;\nlet address = session.address;\nlet paymentMethod = session.paymentMethod;\nlet orderReady = false;\nconst catalog = { H001:{nombre:'Martillo Carpintero 16oz',precio:45000}, H002:{nombre:'Destornillador Estrella #2',precio:12000}, H003:{nombre:'Taladro Inal\u00e1mbrico 18V',precio:280000}, H004:{nombre:'Nivel Aluminio 60cm',precio:35000}, H005:{nombre:'Llave Inglesa 12 pulgadas',precio:28000}, M001:{nombre:'Cemento Gris 50kg',precio:32000}, M002:{nombre:'Arena de R\u00edo bulto 40kg',precio:8500}, M003:{nombre:'Varilla Corrugada 3/8',precio:18000}, M004:{nombre:'Ladrillo Tolete unidad',precio:800}, E001:{nombre:'Cable THW #12 rollo 100m',precio:95000}, E002:{nombre:'Toma Corriente Doble',precio:8500}, E003:{nombre:'Interruptor Sencillo',precio:6500}, P001:{nombre:'Tubo PVC 1/2 x 6m',precio:12000}, P002:{nombre:'Codo PVC 1/2',precio:900}, P003:{nombre:'Llave de Paso 1/2',precio:18500}, F001:{nombre:'Pintura Viniltex Blanco 1/4 GL',precio:22000}, F002:{nombre:'Brocha 3 pulgadas',precio:7500}, F003:{nombre:'Rodillo Felpa 9 pulgadas',precio:14000} };\nconst cart = JSON.parse(JSON.stringify(session.cart || []));\nconst cartRegex = /\\[CART_ACTION:(add|remove)\\|([A-Z0-9]+)\\|(\\d+)\\]/g;\nlet m;\nwhile ((m = cartRegex.exec(rawResponse)) !== null) {\n const [, action, productId, qtyStr] = m;\n const qty = parseInt(qtyStr);\n if (action === 'add' && catalog[productId]) {\n const existing = cart.find(i => i.id === productId);\n if (existing) { existing.qty += qty; } else { cart.push({ id: productId, nombre: catalog[productId].nombre, precio: catalog[productId].precio, qty }); }\n } else if (action === 'remove') { const idx = cart.findIndex(i => i.id === productId); if (idx > -1) cart.splice(idx, 1); }\n reply = reply.replace(m[0], '');\n}\nconst stateM = reply.match(/\\[STATE:(\\w+)\\]/); if (stateM) { newState = stateM[1]; reply = reply.replace(stateM[0], ''); }\nif (reply.includes('[ORDER_READY]')) { orderReady = true; reply = reply.replace('[ORDER_READY]', ''); }\nconst orderM = reply.match(/\\[ORDER_ID:([A-Z0-9-]+)\\]/); if (orderM) { orderId = orderM[1]; reply = reply.replace(orderM[0], ''); }\nconst nameM = reply.match(/\\[CUSTOMER_NAME:([^\\]]+)\\]/); if (nameM) { customerName = nameM[1].trim(); reply = reply.replace(nameM[0], ''); }\nconst addrM = reply.match(/\\[ADDRESS:([^\\]]+)\\]/); if (addrM) { address = addrM[1].trim(); reply = reply.replace(addrM[0], ''); }\nconst payM = reply.match(/\\[PAYMENT:([^\\]]+)\\]/); if (payM) { paymentMethod = payM[1].trim(); reply = reply.replace(payM[0], ''); }\nconst subtotal = cart.reduce((s, i) => s + i.precio * i.qty, 0);\nconst domicilio = subtotal > 0 && subtotal < 150000 ? 15000 : 0;\nconst total = subtotal + domicilio;\nconst updatedSession = { ...session, cart, state: newState, orderId, customerName, address, paymentMethod, subtotal, domicilio, total, history: [...(session.history||[]), { role: 'assistant', content: rawResponse, ts: new Date().toISOString() }] };\nreturn [{ json: { reply: reply.trim(), session: updatedSession, orderReady, from: prevNode.from, wabaId: prevNode.wabaId, order_id: orderId, customer_phone: prevNode.from, customer_name: customerName, cart: JSON.stringify(cart), subtotal, domicilio, total, state: newState, payment_method: paymentMethod, address } }];"
},
"id": "8dee73b8-0afc-4e0c-8572-034a57712b61",
"name": "Procesar Respuesta IA",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1720,
300
]
},
{
"parameters": {
"operation": "set",
"key": "={{ 'ferrebot_session_' + $json.from }}",
"value": "={{ JSON.stringify($json.session) }}",
"keyExpiration": 86400,
"options": {}
},
"id": "439959c4-a7a5-4e95-8888-dbdcee4e9752",
"name": "Guardar Sesi\u00f3n Redis",
"type": "n8n-nodes-base.redis",
"typeVersion": 1,
"position": [
1940,
300
],
"credentials": {
"redis": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "cond-order",
"leftValue": "={{ $json.orderReady }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
],
"combinator": "and"
},
"options": {}
},
"id": "436d6cff-d3f5-4124-a9bf-c204693577e6",
"name": "\u00bfPedido Confirmado?",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
2160,
300
]
},
{
"parameters": {
"operation": "insert",
"schema": {
"__rl": true,
"value": "public",
"mode": "list"
},
"table": {
"__rl": true,
"value": "orders",
"mode": "list"
},
"columns": {
"mappingMode": "defineBelow",
"value": {
"order_id": "={{ $json.order_id }}",
"customer_phone": "={{ $json.customer_phone }}",
"customer_name": "={{ $json.customer_name }}",
"cart": "={{ $json.cart }}",
"subtotal": "={{ $json.subtotal }}",
"domicilio": "={{ $json.domicilio }}",
"total": "={{ $json.total }}",
"state": "={{ $json.state }}",
"payment_method": "={{ $json.payment_method }}",
"address": "={{ $json.address }}"
},
"matchingColumns": [],
"schema": []
},
"options": {}
},
"id": "b011a5fd-bfc7-4b45-bd06-16067f8539ca",
"name": "Guardar Pedido PostgreSQL",
"type": "n8n-nodes-base.postgres",
"typeVersion": 2,
"position": [
2380,
160
],
"credentials": {
"postgres": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"method": "POST",
"url": "=https://graph.facebook.com/v19.0/{{ $('Parsear Mensaje').first().json.wabaId }}/messages",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={\n \"messaging_product\": \"whatsapp\",\n \"recipient_type\": \"individual\",\n \"to\": \"{{ $('Procesar Respuesta IA').first().json.from }}\",\n \"type\": \"text\",\n \"text\": { \"preview_url\": false, \"body\": {{ JSON.stringify($('Procesar Respuesta IA').first().json.reply) }} }\n}",
"options": {
"timeout": 10000
}
},
"id": "95c88a6f-aace-4ca1-a766-df63d3cf718b",
"name": "Enviar Respuesta WhatsApp",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4,
"position": [
2380,
420
],
"credentials": {
"httpHeaderAuth": {
"name": "<your credential>"
}
}
},
{
"parameters": {
"respondWith": "text",
"responseBody": "OK",
"options": {
"responseCode": 200
}
},
"id": "52179986-77fc-46b6-b3a9-f47ab45f9bb2",
"name": "Confirmar Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
2600,
420
]
}
],
"connections": {
"WhatsApp Webhook": {
"main": [
[
{
"node": "Parsear Mensaje",
"type": "main",
"index": 0
}
]
]
},
"Parsear Mensaje": {
"main": [
[
{
"node": "\u00bfMensaje V\u00e1lido?",
"type": "main",
"index": 0
}
]
]
},
"\u00bfMensaje V\u00e1lido?": {
"main": [
[
{
"node": "Obtener Sesi\u00f3n Redis",
"type": "main",
"index": 0
}
],
[
{
"node": "Responder 200 OK",
"type": "main",
"index": 0
}
]
]
},
"Obtener Sesi\u00f3n Redis": {
"main": [
[
{
"node": "Construir Sesi\u00f3n",
"type": "main",
"index": 0
}
]
]
},
"Construir Sesi\u00f3n": {
"main": [
[
{
"node": "Preparar Prompt",
"type": "main",
"index": 0
}
]
]
},
"Preparar Prompt": {
"main": [
[
{
"node": "OpenAI GPT-4o",
"type": "main",
"index": 0
}
]
]
},
"OpenAI GPT-4o": {
"main": [
[
{
"node": "Procesar Respuesta IA",
"type": "main",
"index": 0
}
]
]
},
"Procesar Respuesta IA": {
"main": [
[
{
"node": "Guardar Sesi\u00f3n Redis",
"type": "main",
"index": 0
}
]
]
},
"Guardar Sesi\u00f3n Redis": {
"main": [
[
{
"node": "\u00bfPedido Confirmado?",
"type": "main",
"index": 0
}
]
]
},
"\u00bfPedido Confirmado?": {
"main": [
[
{
"node": "Guardar Pedido PostgreSQL",
"type": "main",
"index": 0
}
],
[
{
"node": "Enviar Respuesta WhatsApp",
"type": "main",
"index": 0
}
]
]
},
"Guardar Pedido PostgreSQL": {
"main": [
[
{
"node": "Enviar Respuesta WhatsApp",
"type": "main",
"index": 0
}
]
]
},
"Enviar Respuesta WhatsApp": {
"main": [
[
{
"node": "Confirmar Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1",
"saveManualExecutions": true
},
"staticData": null,
"meta": {
"templateCredsSetupCompleted": false
}
}
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.
httpHeaderAuthopenAiApipostgresredis
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
FerreBot — Agente Ventas Ferretería WhatsApp. Uses redis, httpRequest, postgres. Webhook trigger; 14 nodes.
Source: https://gist.github.com/panchapinzonsamuel-sketch/a6b50518ca4d4ab3613be51f18c0e33f — 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.
W1 - IN WhatsApp Adapter (Secure + Fast ACK). Uses postgres, redis, httpRequest. Webhook trigger; 50 nodes.
W1 - IN WhatsApp Adapter (Secure + Fast ACK). Uses postgres, redis, httpRequest. Webhook trigger; 48 nodes.
WhatsApp AI Agent - Template. Uses redis, httpRequest, postgres, agent. Webhook trigger; 89 nodes.
Aura-bot. Uses postgres, lmChatOpenAi, memoryBufferWindow, httpRequest. Webhook trigger; 82 nodes.
Brokeria-v20. Uses n8n-nodes-waha, httpRequest, redis, googleGemini. Webhook trigger; 56 nodes.