AutomationFlowsSlack & Telegram › Mercadochat - Fluxo 2: Processamento De Nota Fiscal

Mercadochat - Fluxo 2: Processamento De Nota Fiscal

MercadoChat - Fluxo 2: Processamento de Nota Fiscal. Uses httpRequest, telegram. Webhook trigger; 12 nodes.

Webhook trigger★★★★☆ complexity12 nodesHTTP RequestTelegram
Slack & Telegram Trigger: Webhook Nodes: 12 Complexity: ★★★★☆ Added:

This workflow follows the HTTP Request → Telegram 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": "MercadoChat - Fluxo 2: Processamento de Nota Fiscal",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "mercado-chat-receipt",
        "responseMode": "lastNode",
        "options": {}
      },
      "id": "webhook",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        250,
        400
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.telegram.org/bot8733282592:AAF7232MFeMdRcV6HyHUeKvyTKe71cv8oNw/getFile?file_id={{ $json.photoFileId }}",
        "sendHeaders": false,
        "sendBody": false
      },
      "id": "get-file-path",
      "name": "Obter Caminho do Arquivo (Telegram)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        450,
        400
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "=https://api.telegram.org/file/bot8733282592:AAF7232MFeMdRcV6HyHUeKvyTKe71cv8oNw/{{ $json.result.file_path }}",
        "responseFormat": "file",
        "sendHeaders": false,
        "sendBody": false
      },
      "id": "download-image",
      "name": "Download da Imagem",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        650,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "const item = $input.first();\nconst binaryData = await this.helpers.getBinaryDataBuffer(0, 'data');\nconst base64Image = binaryData.toString('base64');\n\n// Prompt do arquivo 02_receipt_extractor.txt\nconst systemPrompt = `Analise a imagem da nota fiscal enviada pelo usu\u00e1rio e extraia os dados de compra.\\nRetorne APENAS um JSON puro, sem texto adicional, markdown ou explica\u00e7\u00f5es.\\n\\n## Instru\u00e7\u00f5es de extra\u00e7\u00e3o:\\n- Se n\u00e3o conseguir ler um campo com clareza, use null\\n- Pre\u00e7os devem ser n\u00fameros sem s\u00edmbolo de moeda (ex: 12.90)\\n- Datas no formato YYYY-MM-DD\\n- Quantidades como n\u00fameros (ex: 2, 1.5, 0.500)\\n- O nome do produto deve ser extra\u00eddo tal como aparece na NF\\n- CNPJ extraia apenas os n\u00fameros\\n\\n## Formato de resposta:\\n{\\n  \\\"success\\\": true,\\n  \\\"market_name\\\": \\\"<nome>\\\",\\n  \\\"market_cnpj\\\": \\\"<cnpj>\\\",\\n  \\\"market_address\\\": \\\"<endereco>\\\",\\n  \\\"market_city\\\": \\\"<cidade>\\\",\\n  \\\"market_state\\\": \\\"<UF>\\\",\\n  \\\"market_cep\\\": \\\"<cep>\\\",\\n  \\\"purchase_date\\\": \\\"<YYYY-MM-DD>\\\",\\n  \\\"total\\\": <numero>,\\n  \\\"items\\\": [ { \\\"name\\\": \\\"<nome>\\\", \\\"quantity\\\": <numero>, \\\"unit\\\": \\\"<unidade>\\\", \\\"unit_price\\\": <numero>, \\\"total_price\\\": <numero> } ]\\n}`;\n\nreturn [{ json: { base64Image, systemPrompt, originalData: $('Webhook').first().json.body } }];"
      },
      "id": "prepare-vision-prompt",
      "name": "Preparar Base64 e Prompt",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        850,
        400
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "=https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key=<redacted-credential>",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "=body",
              "value": "={{ JSON.stringify({ contents: [{ parts: [{ text: $json.systemPrompt }, { inlineData: { mimeType: 'image/jpeg', data: $json.base64Image } }] }] }) }}"
            }
          ]
        }
      },
      "id": "gemini-vision",
      "name": "Gemini Vision (Extrair NF)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1050,
        400
      ]
    },
    {
      "parameters": {
        "jsCode": "const geminiResp = $input.first().json;\nconst prepData = $('Preparar Base64 e Prompt').first().json;\n\nlet receiptData = { success: false, error: 'Falha ao processar nota fiscal.' };\n\ntry {\n  const rawText = geminiResp?.candidates?.[0]?.content?.parts?.[0]?.text || '{}';\n  const cleanText = rawText.replace(/```json/g, '').replace(/```/g, '').trim();\n  receiptData = JSON.parse(cleanText);\n} catch(e) {\n  console.error('Erro no parse do Gemini', e);\n}\n\nreturn [{ json: { receipt: receiptData, userData: prepData.originalData, rawResponse: geminiResp?.candidates?.[0]?.content?.parts?.[0]?.text } }];"
      },
      "id": "parse-receipt",
      "name": "Parse Dados Extra\u00eddos",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1250,
        400
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {},
          "conditions": [
            {
              "leftValue": "={{ $json.receipt.success }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ]
        }
      },
      "id": "is-success",
      "name": "Sucesso na Extra\u00e7\u00e3o?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1450,
        300
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.userData.chatId }}",
        "text": "={{ '\u274c Ops! ' + ($json.receipt.error || 'N\u00e3o consegui ler essa imagem como uma nota fiscal v\u00e1lida. Pode tentar enviar uma foto mais clara?') }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "send-error",
      "name": "Enviar Erro de Leitura",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1650,
        500
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// Script robusto para salvar Market, Product, Prices, Purchase e Items no Supabase via API REST\n// Esta abordagem via Code node evita a complexidade de m\u00faltiplos n\u00f3s Supabase conectados que podem se perder na itera\u00e7\u00e3o\n\nconst receipt = $json.receipt;\nconst userData = $json.userData;\nconst supabaseUrl = 'https://lhybdghiuafcsvytxhdp.supabase.co';\nconst supabaseKey = '<redacted-credential>';\n\nasync function apiRequest(endpoint, method, body = null) {\n  const options = {\n    headers: {\n      'apikey': supabaseKey,\n      'Authorization': `Bearer ${supabaseKey}`,\n      'Content-Type': 'application/json',\n      'Prefer': 'return=representation'\n    },\n    method: method,\n    url: `${supabaseUrl}/rest/v1/${endpoint}`,\n    json: true\n  };\n  if (body) options.body = body;\n  return await this.helpers.httpRequest(options);\n}\n\ntry {\n  // 1. Processar Mercado (Upsert pelo CNPJ ou Nome)\n  let marketId = null;\n  if (receipt.market_cnpj || receipt.market_name) {\n    const marketQ = receipt.market_cnpj ? `cnpj=eq.${receipt.market_cnpj}` : `name=eq.${encodeURIComponent(receipt.market_name)}`;\n    const existingMarkets = await apiRequest(`markets?${marketQ}&select=id`, 'GET');\n    \n    if (existingMarkets && existingMarkets.length > 0) {\n      marketId = existingMarkets[0].id;\n    } else {\n      const newMarket = await apiRequest(`markets`, 'POST', {\n        name: receipt.market_name || 'Mercado Desconhecido',\n        name_normalized: (receipt.market_name || '').toLowerCase().normalize('NFD').replace(/[\\u0300-\\u036f]/g, ''),\n        cnpj: receipt.market_cnpj,\n        address: receipt.market_address,\n        city: receipt.market_city,\n        state: receipt.market_state,\n        cep: receipt.market_cep\n      });\n      if (newMarket && newMarket.length > 0) marketId = newMarket[0].id;\n    }\n  }\n  \n  // 2. Criar Compra (Purchase)\n  let pointsEarned = 10; // Fixo para MVP (10 pts por cupom)\n  const newPurchase = await apiRequest(`purchases`, 'POST', {\n    user_id: userData.userId,\n    market_id: marketId,\n    total: receipt.total,\n    purchase_date: receipt.purchase_date || new Date().toISOString().split('T')[0],\n    points_earned: pointsEarned,\n    status: 'processed'\n  });\n  const purchaseId = newPurchase[0].id;\n\n  // 3. Processar Itens\n  for (const item of (receipt.items || [])) {\n    if (!item.name) continue;\n    \n    const normalizedName = item.name.toLowerCase().normalize('NFD').replace(/[\\u0300-\\u036f]/g, '');\n    \n    // Tenta achar produto\n    const existingProducts = await apiRequest(`products?name_normalized=eq.${encodeURIComponent(normalizedName)}&select=id`, 'GET');\n    let productId = null;\n    \n    if (existingProducts && existingProducts.length > 0) {\n      productId = existingProducts[0].id;\n    } else {\n      const newProduct = await apiRequest(`products`, 'POST', {\n        name: item.name,\n        name_normalized: normalizedName,\n        unit: item.unit\n      });\n      if (newProduct && newProduct.length > 0) productId = newProduct[0].id;\n    }\n    \n    // Inserir Item da compra\n    await apiRequest(`purchase_items`, 'POST', {\n      purchase_id: purchaseId,\n      product_id: productId,\n      product_name_raw: item.name,\n      quantity: item.quantity || 1,\n      unit_price: item.unit_price,\n      total_price: item.total_price\n    });\n    \n    // Inserir Price Record\n    if (productId && marketId && item.unit_price) {\n      await apiRequest(`price_records`, 'POST', {\n        product_id: productId,\n        market_id: marketId,\n        price: item.unit_price,\n        quantity: item.quantity || 1,\n        unit: item.unit,\n        recorded_at: receipt.purchase_date ? new Date(receipt.purchase_date).toISOString() : new Date().toISOString(),\n        source: 'receipt'\n      });\n    }\n  }\n\n  // 4. Atualizar Pontos do Usu\u00e1rio\n  const newPoints = parseInt(userData.userPoints || 0) + pointsEarned;\n  await apiRequest(`users?id=eq.${userData.userId}`, 'PATCH', { points: newPoints });\n  \n  // Criar hist\u00f3rico de pontos\n  await apiRequest(`points_history`, 'POST', {\n    user_id: userData.userId,\n    points: pointsEarned,\n    description: 'Envio de Nota Fiscal',\n    purchase_id: purchaseId\n  });\n\n  return [{ json: { success: true, pointsEarned, newPoints, itemCount: (receipt.items || []).length, marketName: receipt.market_name, chatId: userData.chatId } }];\n  \n} catch (error) {\n  console.error('Erro ao salvar no DB:', error);\n  return [{ json: { success: false, error: error.message, chatId: userData.chatId } }];\n}"
      },
      "id": "save-to-supabase",
      "name": "Salvar Dados no Supabase (C\u00f3digo)",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1650,
        200
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {},
          "conditions": [
            {
              "leftValue": "={{ $json.success }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ]
        }
      },
      "id": "is-db-success",
      "name": "Sucesso no Banco?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1850,
        200
      ]
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "={{ '\u2705 *Nota Fiscal lida com sucesso!* \ud83c\udf89\\n\\nEstabelecimento: *' + ($json.marketName || 'Desconhecido') + '*\\nItens registrados: *' + $json.itemCount + '*\\n\\n\ud83c\udf81 Voc\u00ea ganhou *+' + $json.pointsEarned + ' pontos* e agora tem um total de *' + $json.newPoints + ' pontos*!\\n\\nOs pre\u00e7os j\u00e1 foram atualizados no sistema e v\u00e3o ajudar outros compradores. Valeu! \ud83d\uded2' }}",
        "additionalFields": {
          "parse_mode": "Markdown"
        }
      },
      "id": "send-success",
      "name": "Enviar Confirma\u00e7\u00e3o e Pontos",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2050,
        100
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $json.chatId }}",
        "text": "\u274c Falha ao processar os dados da sua nota fiscal no nosso sistema. Por favor, tente novamente mais tarde.",
        "additionalFields": {}
      },
      "id": "send-db-error",
      "name": "Enviar Erro de Sistema",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        2050,
        300
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    }
  ],
  "connections": {
    "Execute Workflow Trigger": {
      "main": [
        [
          {
            "node": "Obter Caminho do Arquivo (Telegram)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Obter Caminho do Arquivo (Telegram)": {
      "main": [
        [
          {
            "node": "Download da Imagem",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Download da Imagem": {
      "main": [
        [
          {
            "node": "Preparar Base64 e Prompt",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Preparar Base64 e Prompt": {
      "main": [
        [
          {
            "node": "Gemini Vision (Extrair NF)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gemini Vision (Extrair NF)": {
      "main": [
        [
          {
            "node": "Parse Dados Extra\u00eddos",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse Dados Extra\u00eddos": {
      "main": [
        [
          {
            "node": "Sucesso na Extra\u00e7\u00e3o?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sucesso na Extra\u00e7\u00e3o?": {
      "main": [
        [
          {
            "node": "Salvar Dados no Supabase (C\u00f3digo)",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Enviar Erro de Leitura",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Salvar Dados no Supabase (C\u00f3digo)": {
      "main": [
        [
          {
            "node": "Sucesso no Banco?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sucesso no Banco?": {
      "main": [
        [
          {
            "node": "Enviar Confirma\u00e7\u00e3o e Pontos",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Enviar Erro de Sistema",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [
    "mercado-chat",
    "sub-workflow"
  ],
  "triggerCount": 0,
  "updatedAt": "2026-03-06T00:00:00.000Z",
  "versionId": "2"
}

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

MercadoChat - Fluxo 2: Processamento de Nota Fiscal. Uses httpRequest, telegram. Webhook trigger; 12 nodes.

Source: https://github.com/Shtorache/mercadinho_chat/blob/f596a308e1f9f899d9a51a3df1ece34e9b5b9b34/n8n_workflows/02_fluxo_nota_fiscal.json — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

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

Slack & Telegram

qualiopi. Uses airtable, telegram, emailSend, httpRequest. Webhook trigger; 51 nodes.

Airtable, Telegram, Email Send +3
Slack & Telegram

PsyCardv2. Uses executeCommand, telegram, readBinaryFile, googleDrive. Webhook trigger; 41 nodes.

Execute Command, Telegram, Read Binary File +2
Slack & Telegram

[](https://www.linkedin.com/in/mosaab-yassir-lafrimi/)[](https://t.me/joevenner)

HTTP Request, Redis, S3 +1
Slack & Telegram

How it works • Webhook triggers from content creation system in Airtable • Downloads media (images/videos) from Airtable URLs • Uploads media to Postiz cloud storage • Schedules or publishes content a

Airtable, Telegram, HTTP Request
Slack & Telegram

I wanted to avoid the rush at end of month to log expenses. I tried existing expense apps but found them either too expensive for what they offer, or frustrating with inconsistent extraction results.

HTTP Request, Telegram