AutomationFlowsData & Sheets › Itb Sdr — Mvp

Itb Sdr — Mvp

ITB SDR — MVP. Uses postgres, httpRequest. Webhook trigger; 39 nodes.

Webhook trigger★★★★★ complexity39 nodesPostgresHTTP Request
Data & Sheets Trigger: Webhook Nodes: 39 Complexity: ★★★★★ Added:

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 →

Download .json
{
  "name": "ITB SDR \u2014 MVP",
  "nodes": [
    {
      "id": "note",
      "name": "Card",
      "type": "n8n-nodes-base.stickyNote",
      "typeVersion": 1,
      "position": [
        -640,
        160
      ],
      "parameters": {
        "color": 4,
        "width": 380,
        "height": 260,
        "content": "## SDR-IA IT Booster\nWebhook SimplesZap \u2192 normaliza (texto/m\u00eddia) \u2192 config \u2192 cria lead \u2192 sauda\u00e7\u00e3o (novo) / m\u00eddia (pede texto) / agente DeepSeek.\nAgente decide A\u00c7\u00c3O: conversar | agendar (notifica Inael + call_agendada) | escalar (handoff + notifica).\nTudo parametriz\u00e1vel em itbooster_sdr_config."
      }
    },
    {
      "id": "wh",
      "name": "1\u00b7 Webhook inbound",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        -240,
        80
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "itb-sdr/inbound",
        "responseMode": "responseNode",
        "options": {}
      }
    },
    {
      "id": "norm",
      "name": "2\u00b7 Normalizar",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -40,
        80
      ],
      "parameters": {
        "jsCode": "const h=$input.first().json.headers||{};\nconst b=$input.first().json.body||{};\nconst event=(h['x-webhook-event']||'').toString();\nconst isMessage=event==='message.received';\nconst mediaEvents=['message.audio.received','message.image.received','message.video.received','message.document.received','audio.received','image.received','video.received','document.received'];\nconst isMedia=mediaEvents.includes(event);\nlet phone=(b.from??b.number??'').toString().replace(/[^0-9]/g,'');\nif(phone.length===12&&phone.startsWith('55')) phone=phone.slice(0,4)+'9'+phone.slice(4);\nconst texto=isMessage?(b.text??'').toString():'';\nconst fromMe=Boolean(b.fromMe);\nconst isGroup=/@g\\.us/.test((b.from??'').toString());\nconst processar=((isMessage&&texto.length>0)||isMedia)&&!fromMe&&!isGroup;\nreturn [{json:{event,isMessage,isMedia,processar,phone,texto,tipo:(b.type??'text').toString(),messageId:(b.messageId??'').toString(),nome:(b.fromName??b.name??'').toString(),mediaUrl:(b.mediaUrl??'').toString(),ctwa_clid:(b.referral?.ctwa_clid??b.ctwa_clid??'').toString(),fbc:(b.fbc??'').toString(),fbp:(b.fbp??'').toString()}}];"
      }
    },
    {
      "id": "cfg",
      "name": "0\u00b7 Config",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        160,
        80
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "select * from itbooster_sdr_config where id=1;",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "proc",
      "name": "3\u00b7 Process\u00e1vel?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        360,
        80
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "leftValue": "={{ $('2\u00b7 Normalizar').first().json.processar }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "ups",
      "name": "4\u00b7 UPSERT lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        560,
        60
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "with up_lead as (\n insert into itbooster_sdr_leads (phone,nome,origem,status,ctwa_clid,fbc,fbp)\n values ('{{ $('2\u00b7 Normalizar').first().json.phone }}', $${{ $('2\u00b7 Normalizar').first().json.nome }}$$,'meta-ads','novo', nullif($${{ $('2\u00b7 Normalizar').first().json.ctwa_clid }}$$,''), nullif($${{ $('2\u00b7 Normalizar').first().json.fbc }}$$,''), nullif($${{ $('2\u00b7 Normalizar').first().json.fbp }}$$,''))\n on conflict (phone) do update set nome=coalesce(itbooster_sdr_leads.nome,excluded.nome), ctwa_clid=coalesce(itbooster_sdr_leads.ctwa_clid,excluded.ctwa_clid)\n returning id,(xmax=0) as created\n), up_conv as (\n insert into itbooster_sdr_conversations (phone,lead_id,current_step) select '{{ $('2\u00b7 Normalizar').first().json.phone }}',id,'NEW_LEAD' from up_lead on conflict (phone) do update set updated_at=now() returning is_ai_managed,current_step\n)\n select l.created,c.is_ai_managed,c.current_step,'{{ $('2\u00b7 Normalizar').first().json.phone }}' as phone, $${{ $('2\u00b7 Normalizar').first().json.texto }}$$ as texto, $${{ $('2\u00b7 Normalizar').first().json.isMedia }}$$ as ismedia from up_lead l cross join up_conv c;",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "ai",
      "name": "5\u00b7 IA no controle?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        760,
        60
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "leftValue": "={{ $json.is_ai_managed }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "ismedia",
      "name": "6\u00b7 \u00c9 m\u00eddia?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        960,
        60
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "leftValue": "={{ $('2\u00b7 Normalizar').first().json.isMedia }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "mreply",
      "name": "7\u00b7 Resposta m\u00eddia",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1160,
        -80
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('0\u00b7 Config').first().json.simpleszap_base_url }}/message/sendText/{{ $('0\u00b7 Config').first().json.simpleszap_instance_id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"number\": \"{{ $('4\u00b7 UPSERT lead').first().json.phone }}\",\n \"text\": \"Recebi seu arquivo! \ud83d\ude4c Pra te ajudar certinho, me conta por texto rapidinho o que voc\u00ea precisa?\"\n}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "mlog",
      "name": "8\u00b7 Log m\u00eddia",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1360,
        -80
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "insert into itbooster_sdr_messages (phone,direction,msg_type,body) values ('{{ $('4\u00b7 UPSERT lead').first().json.phone }}','in','{{ $('2\u00b7 Normalizar').first().json.tipo }}','[m\u00eddia recebida]'),('{{ $('4\u00b7 UPSERT lead').first().json.phone }}','out','text','pediu texto');",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "isaudio",
      "name": "6b\u00b7 \u00c9 \u00e1udio?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1160,
        -280
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "leftValue": "={{ $('2\u00b7 Normalizar').first().json.tipo }}",
              "rightValue": "audio",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "tr1",
      "name": "tr1\u00b7 Evolution \u00e1udio",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1360,
        -360
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('0\u00b7 Config').first().json.evolution_base_url }}/chat/getBase64FromMediaMessage/{{ $('0\u00b7 Config').first().json.evolution_instance }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $('0\u00b7 Config').first().json.evolution_apikey }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"message\": { \"key\": { \"id\": \"{{ $('2\u00b7 Normalizar').first().json.messageId }}\" } },\n \"convertToMp4\": false\n}",
        "options": {}
      }
    },
    {
      "id": "tr2",
      "name": "tr2\u00b7 Base64 p/ arquivo",
      "type": "n8n-nodes-base.convertToFile",
      "typeVersion": 1.1,
      "position": [
        1560,
        -360
      ],
      "parameters": {
        "operation": "toBinary",
        "sourceProperty": "base64",
        "options": {
          "fileName": "audio.ogg",
          "mimeType": "audio/ogg"
        }
      }
    },
    {
      "id": "tr3",
      "name": "tr3\u00b7 Groq Whisper",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1760,
        -360
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.groq.com/openai/v1/audio/transcriptions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('0\u00b7 Config').first().json.groq_api_key }}"
            }
          ]
        },
        "sendBody": true,
        "contentType": "multipart-form-data",
        "bodyParameters": {
          "parameters": [
            {
              "parameterType": "formBinaryData",
              "name": "file",
              "inputDataFieldName": "data"
            },
            {
              "parameterType": "formData",
              "name": "model",
              "value": "whisper-large-v3"
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "tr4",
      "name": "tr4\u00b7 Reinjeta texto",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1960,
        -360
      ],
      "parameters": {
        "method": "POST",
        "url": "https://n8n.toolpad.cloud/webhook/itb-sdr/inbound",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-webhook-event",
              "value": "message.received"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"instanceId\": \"{{ $('0\u00b7 Config').first().json.simpleszap_instance_id }}\",\n \"messageId\": \"{{ $('2\u00b7 Normalizar').first().json.messageId }}-tr\",\n \"from\": \"{{ $('4\u00b7 UPSERT lead').first().json.phone }}\",\n \"fromName\": \"{{ $('2\u00b7 Normalizar').first().json.nome }}\",\n \"fromMe\": false,\n \"type\": \"text\",\n \"text\": {{ JSON.stringify($json.text) }}\n}",
        "options": {}
      }
    },
    {
      "id": "isimg",
      "name": "6c\u00b7 \u00c9 imagem?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1360,
        -160
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "leftValue": "={{ $('2\u00b7 Normalizar').first().json.tipo }}",
              "rightValue": "image",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "im1",
      "name": "im1\u00b7 Evolution imagem",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1560,
        -160
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('0\u00b7 Config').first().json.evolution_base_url }}/chat/getBase64FromMediaMessage/{{ $('0\u00b7 Config').first().json.evolution_instance }}",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "apikey",
              "value": "={{ $('0\u00b7 Config').first().json.evolution_apikey }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"message\": { \"key\": { \"id\": \"{{ $('2\u00b7 Normalizar').first().json.messageId }}\" } }\n}",
        "options": {}
      }
    },
    {
      "id": "im2",
      "name": "im2\u00b7 Groq vis\u00e3o",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1760,
        -160
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "=Bearer {{ $('0\u00b7 Config').first().json.groq_api_key }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"model\": \"meta-llama/llama-4-scout-17b-16e-instruct\",\n \"max_tokens\": 150,\n \"messages\": [{ \"role\": \"user\", \"content\": [ { \"type\": \"text\", \"text\": \"Descreva esta imagem em 1-2 frases curtas, em portugues, focando no que importa pra um negocio (print de tela, foto de produto, documento, etc).\" }, { \"type\": \"image_url\", \"image_url\": { \"url\": \"data:{{ ($json.mimetype||'image/jpeg').split(';')[0] }};base64,{{ $json.base64 }}\" } } ] }]\n}",
        "options": {}
      }
    },
    {
      "id": "im3",
      "name": "im3\u00b7 Reinjeta imagem",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1960,
        -160
      ],
      "parameters": {
        "method": "POST",
        "url": "https://n8n.toolpad.cloud/webhook/itb-sdr/inbound",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "x-webhook-event",
              "value": "message.received"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"instanceId\": \"{{ $('0\u00b7 Config').first().json.simpleszap_instance_id }}\",\n \"messageId\": \"{{ $('2\u00b7 Normalizar').first().json.messageId }}-img\",\n \"from\": \"{{ $('4\u00b7 UPSERT lead').first().json.phone }}\",\n \"fromName\": \"{{ $('2\u00b7 Normalizar').first().json.nome }}\",\n \"fromMe\": false,\n \"type\": \"text\",\n \"text\": {{ JSON.stringify('[imagem] ' + ($json.choices[0].message.content||'')) }}\n}",
        "options": {}
      }
    },
    {
      "id": "gcal",
      "name": "18b\u00b7 Cria evento GCal",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2560,
        60
      ],
      "parameters": {
        "jsCode": "const crypto=require('crypto');const cfg=$('0\u00b7 Config').first().json;const r=$('15\u00b7 Ler resposta IA').first().json;let startD=new Date(r.slot);if(!r.slot||isNaN(startD.getTime())){startD=new Date(Date.now()+86400000);startD.setHours(13,0,0,0);}const start=startD.toISOString();const end=new Date(startD.getTime()+1800000).toISOString();const b64=(s)=>Buffer.from(s).toString('base64url');const now=Math.floor(Date.now()/1000);const si=b64(JSON.stringify({alg:'RS256',typ:'JWT'}))+'.'+b64(JSON.stringify({iss:cfg.gcal_sa_email,scope:'https://www.googleapis.com/auth/calendar',aud:'https://oauth2.googleapis.com/token',iat:now,exp:now+3600}));const signer=crypto.createSign('RSA-SHA256');signer.update(si);const jwt=si+'.'+signer.sign(cfg.gcal_sa_key).toString('base64url');const tok=await this.helpers.httpRequest({method:'POST',url:'https://oauth2.googleapis.com/token',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion='+jwt});const token=(typeof tok==='string'?JSON.parse(tok):tok).access_token;const meet='https://meet.jit.si/itbooster-'+r.phone;const ev={summary:'Call de diagnostico - lead '+r.phone,description:'Lead: '+r.phone+' | Segmento: '+(r.segmento||'')+' | Dor: '+(r.dor||'')+' | Link: '+meet,start:{dateTime:start,timeZone:'America/Sao_Paulo'},end:{dateTime:end,timeZone:'America/Sao_Paulo'}};const created=await this.helpers.httpRequest({method:'POST',url:'https://www.googleapis.com/calendar/v3/calendars/'+encodeURIComponent(cfg.gcal_calendar_id)+'/events',headers:{Authorization:'Bearer '+token,'Content-Type':'application/json'},body:ev,json:true});return [{json:{event_link:created.htmlLink||'',meet,start}}];"
      }
    },
    {
      "id": "novo",
      "name": "9\u00b7 Lead novo?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1160,
        140
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "leftValue": "={{ $json.created }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "saud",
      "name": "10\u00b7 Sauda\u00e7\u00e3o",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1360,
        60
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('0\u00b7 Config').first().json.simpleszap_base_url }}/message/sendText/{{ $('0\u00b7 Config').first().json.simpleszap_instance_id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"number\": \"{{ $json.phone }}\",\n \"text\": {{ JSON.stringify($('0\u00b7 Config').first().json.saudacao) }}\n}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "slog",
      "name": "11\u00b7 Log sauda\u00e7\u00e3o",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1560,
        60
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "insert into itbooster_sdr_messages (phone,direction,msg_type,body) values ('{{ $('4\u00b7 UPSERT lead').first().json.phone }}','out','text', $${{ $('0\u00b7 Config').first().json.saudacao }}$$); update itbooster_sdr_conversations set current_step='OPENED', last_outbound_at=now() where phone='{{ $('4\u00b7 UPSERT lead').first().json.phone }}';",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "buf",
      "name": "12\u00b7 Buffer (debounce)",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1360,
        420
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "insert into itbooster_sdr_messages (phone,direction,msg_type,body) values ('{{ $('4\u00b7 UPSERT lead').first().json.phone }}','in','text', $${{ $('4\u00b7 UPSERT lead').first().json.texto }}$$); update itbooster_sdr_conversations set debounce_token='{{ $('2\u00b7 Normalizar').first().json.messageId }}' where phone='{{ $('4\u00b7 UPSERT lead').first().json.phone }}'; select 1;",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "wait",
      "name": "12b\u00b7 Aguarda 8s",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        1560,
        420
      ],
      "parameters": {
        "amount": 10,
        "unit": "seconds"
      }
    },
    {
      "id": "chk",
      "name": "12c\u00b7 Checa \u00faltima",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        1760,
        420
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "select (debounce_token = '{{ $('2\u00b7 Normalizar').first().json.messageId }}') as latest from itbooster_sdr_conversations where phone='{{ $('4\u00b7 UPSERT lead').first().json.phone }}';",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "islast",
      "name": "12d\u00b7 \u00c9 a \u00faltima?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1960,
        420
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "leftValue": "={{ $json.latest }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ]
        },
        "options": {}
      }
    },
    {
      "id": "hist",
      "name": "12e\u00b7 Hist\u00f3rico",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        2160,
        360
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "select (select string_agg((case when direction='in' then 'Lead: ' else 'SDR: ' end)||body, E'\\n' order by created_at) from (select * from itbooster_sdr_messages where phone='{{ $('4\u00b7 UPSERT lead').first().json.phone }}' order by created_at desc limit 20) t) as historico, (select string_agg(body, E'\\n' order by created_at) from itbooster_sdr_messages where phone='{{ $('4\u00b7 UPSERT lead').first().json.phone }}' and direction='in' and created_at > coalesce((select last_outbound_at from itbooster_sdr_conversations where phone='{{ $('4\u00b7 UPSERT lead').first().json.phone }}'), to_timestamp(0))) as novas;",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "prompt",
      "name": "13\u00b7 Montar prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1560,
        260
      ],
      "parameters": {
        "jsCode": "const cfg=$('0\u00b7 Config').first().json;\nconst phone=$('4\u00b7 UPSERT lead').first().json.phone;\nconst texto=($json.novas||$('4\u00b7 UPSERT lead').first().json.texto);\nconst hist=$json.historico||'';\nconst base=(cfg.prompt_compilado&&String(cfg.prompt_compilado).trim())?cfg.prompt_compilado:[cfg.persona||'',`Empresa: ${cfg.empresa||''}. Site: ${cfg.site||''}.`,`Objetivo: ${cfg.objetivo||''}`,`P\u00fablico: ${cfg.publico||''}`,`Qualifica\u00e7\u00e3o (uma pergunta por vez):\\n${cfg.perguntas||''}`,`Investimento: ${cfg.faixas||''}`,`Regras:\\n${cfg.regras||''}`,`NUNCA:\\n${cfg.nao_pode||''}`].join('\\n\\n');\nconst system=base+`\\n\\nDECIDA A A\u00c7\u00c3O no campo acao: 'conversar' (continuar qualificando), 'agendar' (o lead quer/aceitou uma call de diagn\u00f3stico \u2014 proponha ou confirme um dia/hor\u00e1rio e coloque em slot), 'escalar' (lead pede falar com humano, est\u00e1 irritado, ou foge muito do escopo). \\nResponda SEMPRE em JSON puro: reply, nome (o nome do cliente, se ele informar), status (novo|qualificando|qualificado), segmento, dor, objetivo, faixa_investimento, acao (conversar|agendar|escalar), slot (se acao=agendar, a data e hora em ISO 8601 com fuso -03:00, por exemplo 2026-06-30T10:00:00-03:00, calculada a partir de hoje que e ${new Date().toISOString().slice(0,10)}; senao vazio). Use string vazia no que nao souber.`;\nreturn [{json:{phone,modelo:cfg.modelo||'deepseek-chat',messages:[{role:'system',content:system},{role:'user',content:`Hist\u00f3rico:\\n${hist}\\n\\n\u00daltima mensagem do lead: ${texto}`}]}}];"
      }
    },
    {
      "id": "llm",
      "name": "14\u00b7 DeepSeek",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1760,
        260
      ],
      "parameters": {
        "method": "POST",
        "url": "https://api.deepseek.com/v1/chat/completions",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"model\": {{ JSON.stringify($json.modelo) }},\n \"messages\": {{ JSON.stringify($json.messages) }},\n \"temperature\": 0.5,\n \"response_format\": { \"type\": \"json_object\" }\n}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "parse",
      "name": "15\u00b7 Ler resposta IA",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1960,
        260
      ],
      "parameters": {
        "jsCode": "const phone=$('13\u00b7 Montar prompt').first().json.phone;\nlet o={};\ntry{o=JSON.parse($json.choices[0].message.content);}catch(e){o={reply:$json.choices?.[0]?.message?.content||'Pode me contar um pouco mais?'};}\nconst acao=['conversar','agendar','escalar'].includes(o.acao)?o.acao:'conversar';\nconst status=acao==='agendar'?'call_agendada':(o.status||'qualificando');\nreturn [{json:{phone,reply:o.reply||'Entendi! Me conta mais um pouco?',nome:o.nome||'',status,segmento:o.segmento||'',dor:o.dor||'',objetivo:o.objetivo||'',faixa_investimento:o.faixa_investimento||'',acao,slot:o.slot||''}}];"
      }
    },
    {
      "id": "upd",
      "name": "16\u00b7 Atualiza lead",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        2160,
        260
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "update itbooster_sdr_leads set status='{{ $json.status }}', nome=coalesce(nullif($${{ $json.nome }}$$,''), nome), segmento=nullif($${{ $json.segmento }}$$,''), dor=nullif($${{ $json.dor }}$$,''), objetivo=nullif($${{ $json.objetivo }}$$,''), faixa_investimento=nullif($${{ $json.faixa_investimento }}$$,''), notas=nullif($${{ $json.slot }}$$,'') where phone='{{ $json.phone }}';",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "acao",
      "name": "17\u00b7 A\u00e7\u00e3o?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        2360,
        260
      ],
      "parameters": {
        "rules": {
          "values": [
            {
              "outputKey": "agendar",
              "conditions": {
                "options": {
                  "version": 2,
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "leftValue": "={{ $('15\u00b7 Ler resposta IA').first().json.acao }}",
                    "rightValue": "agendar",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true
            },
            {
              "outputKey": "escalar",
              "conditions": {
                "options": {
                  "version": 2,
                  "caseSensitive": true,
                  "typeValidation": "loose"
                },
                "combinator": "and",
                "conditions": [
                  {
                    "leftValue": "={{ $('15\u00b7 Ler resposta IA').first().json.acao }}",
                    "rightValue": "escalar",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      }
    },
    {
      "id": "mcall",
      "name": "18\u00b7 Marca call agendada",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        2560,
        140
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "update itbooster_sdr_conversations set current_step='SCHEDULED' where phone='{{ $('15\u00b7 Ler resposta IA').first().json.phone }}'; select 1;",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "mhand",
      "name": "19\u00b7 Marca handoff",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        2560,
        300
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "update itbooster_sdr_conversations set is_ai_managed=false, current_step='HUMAN' where phone='{{ $('15\u00b7 Ler resposta IA').first().json.phone }}'; select 1;",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "notify",
      "name": "20\u00b7 Notifica Inael",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2760,
        220
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('0\u00b7 Config').first().json.simpleszap_base_url }}/message/sendText/{{ $('0\u00b7 Config').first().json.simpleszap_instance_id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"number\": \"{{ $('0\u00b7 Config').first().json.notify_phone }}\",\n \"text\": {{ JSON.stringify('\ud83d\udd14 SDR ['+$('15\u00b7 Ler resposta IA').first().json.acao+'] lead '+$('15\u00b7 Ler resposta IA').first().json.phone+'\\nSegmento: '+$('15\u00b7 Ler resposta IA').first().json.segmento+'\\nDor: '+$('15\u00b7 Ler resposta IA').first().json.dor+($('15\u00b7 Ler resposta IA').first().json.slot?'\\nHor\u00e1rio: '+$('15\u00b7 Ler resposta IA').first().json.slot:'')) }}\n}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "reply",
      "name": "21\u00b7 Enviar resposta",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2980,
        260
      ],
      "parameters": {
        "method": "POST",
        "url": "={{ $('0\u00b7 Config').first().json.simpleszap_base_url }}/message/sendText/{{ $('0\u00b7 Config').first().json.simpleszap_instance_id }}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n \"number\": \"{{ $('15\u00b7 Ler resposta IA').first().json.phone }}\",\n \"text\": {{ JSON.stringify($('15\u00b7 Ler resposta IA').first().json.reply) }}\n}",
        "options": {}
      },
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "olog",
      "name": "22\u00b7 Log resposta",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.6,
      "position": [
        3180,
        260
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "insert into itbooster_sdr_messages (phone,direction,msg_type,body) values ('{{ $('15\u00b7 Ler resposta IA').first().json.phone }}','out','text', $${{ $('15\u00b7 Ler resposta IA').first().json.reply }}$$); update itbooster_sdr_conversations set last_outbound_at=now() where phone='{{ $('15\u00b7 Ler resposta IA').first().json.phone }}';",
        "options": {}
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      }
    },
    {
      "id": "resp",
      "name": "Responde 200",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1.1,
      "position": [
        3380,
        160
      ],
      "parameters": {
        "respondWith": "text",
        "responseBody": "ok",
        "options": {}
      }
    }
  ],
  "connections": {
    "1\u00b7 Webhook inbound": {
      "main": [
        [
          {
            "node": "2\u00b7 Normalizar",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "2\u00b7 Normalizar": {
      "main": [
        [
          {
            "node": "0\u00b7 Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "0\u00b7 Config": {
      "main": [
        [
          {
            "node": "3\u00b7 Process\u00e1vel?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "3\u00b7 Process\u00e1vel?": {
      "main": [
        [
          {
            "node": "4\u00b7 UPSERT lead",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Responde 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "4\u00b7 UPSERT lead": {
      "main": [
        [
          {
            "node": "5\u00b7 IA no controle?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "5\u00b7 IA no controle?": {
      "main": [
        [
          {
            "node": "6\u00b7 \u00c9 m\u00eddia?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Responde 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6\u00b7 \u00c9 m\u00eddia?": {
      "main": [
        [
          {
            "node": "6b\u00b7 \u00c9 \u00e1udio?",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "9\u00b7 Lead novo?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6b\u00b7 \u00c9 \u00e1udio?": {
      "main": [
        [
          {
            "node": "tr1\u00b7 Evolution \u00e1udio",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "6c\u00b7 \u00c9 imagem?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "6c\u00b7 \u00c9 imagem?": {
      "main": [
        [
          {
            "node": "im1\u00b7 Evolution imagem",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "7\u00b7 Resposta m\u00eddia",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "im1\u00b7 Evolution imagem": {
      "main": [
        [
          {
            "node": "im2\u00b7 Groq vis\u00e3o",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "im2\u00b7 Groq vis\u00e3o": {
      "main": [
        [
          {
            "node": "im3\u00b7 Reinjeta imagem",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "im3\u00b7 Reinjeta imagem": {
      "main": [
        [
          {
            "node": "Responde 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "tr1\u00b7 Evolution \u00e1udio": {
      "main": [
        [
          {
            "node": "tr2\u00b7 Base64 p/ arquivo",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "tr2\u00b7 Base64 p/ arquivo": {
      "main": [
        [
          {
            "node": "tr3\u00b7 Groq Whisper",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "tr3\u00b7 Groq Whisper": {
      "main": [
        [
          {
            "node": "tr4\u00b7 Reinjeta texto",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "tr4\u00b7 Reinjeta texto": {
      "main": [
        [
          {
            "node": "Responde 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "7\u00b7 Resposta m\u00eddia": {
      "main": [
        [
          {
            "node": "8\u00b7 Log m\u00eddia",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "8\u00b7 Log m\u00eddia": {
      "main": [
        [
          {
            "node": "Responde 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "9\u00b7 Lead novo?": {
      "main": [
        [
          {
            "node": "10\u00b7 Sauda\u00e7\u00e3o",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "12\u00b7 Buffer (debounce)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12\u00b7 Buffer (debounce)": {
      "main": [
        [
          {
            "node": "12b\u00b7 Aguarda 8s",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12b\u00b7 Aguarda 8s": {
      "main": [
        [
          {
            "node": "12c\u00b7 Checa \u00faltima",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12c\u00b7 Checa \u00faltima": {
      "main": [
        [
          {
            "node": "12d\u00b7 \u00c9 a \u00faltima?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12d\u00b7 \u00c9 a \u00faltima?": {
      "main": [
        [
          {
            "node": "12e\u00b7 Hist\u00f3rico",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Responde 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "10\u00b7 Sauda\u00e7\u00e3o": {
      "main": [
        [
          {
            "node": "11\u00b7 Log sauda\u00e7\u00e3o",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "11\u00b7 Log sauda\u00e7\u00e3o": {
      "main": [
        [
          {
            "node": "Responde 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "12e\u00b7 Hist\u00f3rico": {
      "main": [
        [
          {
            "node": "13\u00b7 Montar prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "13\u00b7 Montar prompt": {
      "main": [
        [
          {
            "node": "14\u00b7 DeepSeek",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "14\u00b7 DeepSeek": {
      "main": [
        [
          {
            "node": "15\u00b7 Ler resposta IA",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "15\u00b7 Ler resposta IA": {
      "main": [
        [
          {
            "node": "16\u00b7 Atualiza lead",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "16\u00b7 Atualiza lead": {
      "main": [
        [
          {
            "node": "17\u00b7 A\u00e7\u00e3o?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "17\u00b7 A\u00e7\u00e3o?": {
      "main": [
        [
          {
            "node": "18\u00b7 Marca call agendada",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "19\u00b7 Marca handoff",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "21\u00b7 Enviar resposta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "18\u00b7 Marca call agendada": {
      "main": [
        [
          {
            "node": "18b\u00b7 Cria evento GCal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "18b\u00b7 Cria evento GCal": {
      "main": [
        [
          {
            "node": "20\u00b7 Notifica Inael",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "19\u00b7 Marca handoff": {
      "main": [
        [
          {
            "node": "20\u00b7 Notifica Inael",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "20\u00b7 Notifica Inael": {
      "main": [
        [
          {
            "node": "21\u00b7 Enviar resposta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "21\u00b7 Enviar resposta": {
      "main": [
        [
          {
            "node": "22\u00b7 Log resposta",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "22\u00b7 Log resposta": {
      "main": [
        [
          {
            "node": "Responde 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  }
}

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

ITB SDR — MVP. Uses postgres, httpRequest. Webhook trigger; 39 nodes.

Source: https://gist.github.com/inael/8f442778f360d05355dfecc392e9da31 — original creator credit. Request a take-down →

More Data & Sheets workflows → · Browse all categories →

Related workflows

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

Data & Sheets

CMM. Uses httpRequest, postgres, redis. Webhook trigger; 90 nodes.

HTTP Request, Postgres, Redis
Data & Sheets

Scraping. Uses httpRequest, postgres, @apify/n8n-nodes-apify, respondToWebhook. Webhook trigger; 61 nodes.

HTTP Request, Postgres, @Apify/N8N Nodes Apify
Data & Sheets

Workflow B — AI Listing Engine. Uses httpRequest, postgres, errorTrigger. Webhook trigger; 47 nodes.

HTTP Request, Postgres, Error Trigger
Data & Sheets

LogSentinel Workflow. Uses postgres, emailSend, httpRequest. Webhook trigger; 44 nodes.

Postgres, Email Send, HTTP Request
Data & Sheets

Post-Prayer. Uses postgres, httpRequest. Webhook trigger; 44 nodes.

Postgres, HTTP Request