Fluxo n8n: API, WhatsApp, Log (com dedupe e tratamento de erro)

GitHub n8n

O cenário (realista)

Você quer receber um evento via API (Webhook), enviar uma mensagem no WhatsApp via Notifish e registrar o resultado (sucesso/erro) em log.

Esse fluxo é útil para:

  • alertas operacionais
  • notificações de deploy
  • falhas de integração
  • notificações de sistema para grupos

Pré-requisitos

  1. n8n rodando e acessível
  2. endpoint do Notifish (o seu padrão):
    POST https://meu-dominio.notifish.com/api/v2/{INSTANCE_UUID}/whatsapp/message/groups
    com header Authorization: Bearer ...
  3. um lugar para log
    Vou te dar 2 opções:
  • A) log em arquivo via HTTP (endpoint seu) — mais profissional
  • B) log no próprio n8n (Execution data) + uma planilha/banco (se preferir)

Aqui vou usar a opção A (mais “produção”).


Parte 1 — Contrato da API (Webhook)

Você vai expor um endpoint no n8n, por exemplo:

POST https://SEU_N8N/webhook/notify

Payload recomendado (simples e suficiente):

{
  "title": "API 5xx alto",
  "message": "Erros 5xx acima do normal em /api/orders",
  "url": "https://seu-dashboard",
  "identifier": "prod:api:5xx",
  "delayMessage": 0
}

Repare que já mandamos identifier. Isso permite deduplicar e evitar spam.


Parte 2 — Dedupe (anti-spam) usando Redis (opcional, mas recomendado)

Se você tiver Redis (muito comum), dá para usar um passo “check” antes do envio.

  • chave: dedupe:{identifier}
  • TTL: 300s (5 minutos) por exemplo
    Se existir, você não envia.

Se você não tiver Redis, dá para deduplicar com “Data Store” do n8n ou com um endpoint seu.

Vou te passar o fluxo com Data Store do n8n (não depende de infra extra).


Parte 3 — Enviar WhatsApp via Notifish

O Notifish recebe:

{
  "message": "...",
  "identifier": "...",
  "link": true,
  "typing": "composing",
  "delayMessage": 1200
}

A gente vai montar isso no n8n.


Parte 4 — Registrar Log

Depois do envio (sucesso ou falha), registramos em um endpoint de log. Exemplo:

POST https://seu-sistema.com/api/logs/notifish

Log recomendado:

{
  "identifier": "prod:api:5xx",
  "status": "success",
  "http_status": 200,
  "provider": "notifish",
  "payload": { ... },
  "response": { ... },
  "created_at": "2026-01-22T00:00:00-03:00"
}

NODES DO WORKFLOW (passo a passo)

1) Webhook (Trigger)

  • Method: POST
  • Path: notify
  • Response: JSON (200)

2) Function (Normalize)

Cria um formato consistente e monta a mensagem final do WhatsApp:

  • título + mensagem + url
  • garante identifier
  • define delayMessage

3) Data Store (Get)

Busca identifier na datastore (dedupe).

4) IF (Já enviou?)

Se já existe registro recente, responde 200 “ignored” e não envia.

5) HTTP Request (Notifish)

POST no endpoint do Notifish com Bearer Token.

6) Data Store (Set)

Salva identifier com TTL (ex.: 300s) para dedupe.

7) HTTP Request (Log Success)

Envia log para seu endpoint.

8) Error Trigger / Catch (Log Error)

Se Notifish falhar, manda log com status error.


JSON do workflow para importar no n8n

Abaixo um workflow base. Você só precisa trocar:

  • NOTIFISH_INSTANCE_UUID
  • NOTIFISH_TOKEN
  • LOG_ENDPOINT_URL

Import: n8n → Workflows → Import from file/clipboard

{
  "name": "API → Notifish WhatsApp → Log (com dedupe)",
  "nodes": [
    {
      "parameters": {
        "path": "notify",
        "httpMethod": "POST",
        "responseMode": "lastNode",
        "options": {}
      },
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [260, 300]
    },
    {
      "parameters": {
        "functionCode": "const body = $json;\n\nconst title = body.title || 'Notificação';\nconst msg = body.message || '';\nconst url = body.url || '';\n\nconst identifier = body.identifier || `generic:${Date.now()}`;\nconst delayMessage = body.delayMessage ?? 0;\n\nconst whatsappMessage = `*${title}*\\n\\n${msg}${url ? `\\n\\n${url}` : ''}`;\n\nreturn [{\n  identifier,\n  delayMessage,\n  notifishPayload: {\n    message: whatsappMessage,\n    identifier,\n    link: true,\n    typing: 'composing',\n    delayMessage\n  },\n  original: body\n}];"
      },
      "name": "Normalize",
      "type": "n8n-nodes-base.function",
      "typeVersion": 2,
      "position": [520, 300]
    },
    {
      "parameters": {
        "operation": "get",
        "key": "={{$json.identifier}}"
      },
      "name": "Dedupe Get",
      "type": "n8n-nodes-base.dataStore",
      "typeVersion": 1,
      "position": [760, 300]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.value !== undefined && $json.value !== null}}",
              "value2": true
            }
          ]
        }
      },
      "name": "Já enviou?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [980, 300]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\"status\":\"ignored\",\"reason\":\"dedupe\",\"identifier\":\"{{$json.identifier}}\"}",
        "options": {}
      },
      "name": "Return Ignored",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1210, 200]
    },
    {
      "parameters": {
        "url": "https://meu-dominio.notifish.com/api/v2/NOTIFISH_INSTANCE_UUID/whatsapp/message/groups",
        "method": "POST",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Authorization",
              "value": "Bearer NOTIFISH_TOKEN"
            },
            {
              "name": "Content-Type",
              "value": "application/json"
            },
            {
              "name": "Accept",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "jsonBody": "={{$json.notifishPayload}}",
        "options": {
          "timeout": 8000
        }
      },
      "name": "Send Notifish",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1210, 380]
    },
    {
      "parameters": {
        "operation": "set",
        "key": "={{$json.identifier}}",
        "value": "={{JSON.stringify({sentAt: new Date().toISOString()})}}"
      },
      "name": "Dedupe Set",
      "type": "n8n-nodes-base.dataStore",
      "typeVersion": 1,
      "position": [1450, 380]
    },
    {
      "parameters": {
        "url": "LOG_ENDPOINT_URL",
        "method": "POST",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "contentType": "json",
        "jsonBody": "={{ {\n  identifier: $json.identifier,\n  status: 'success',\n  provider: 'notifish',\n  response: $json,\n  created_at: new Date().toISOString()\n} }}"
      },
      "name": "Log Success",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [1690, 380]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\"status\":\"sent\",\"identifier\":\"{{$json.identifier}}\"}",
        "options": {}
      },
      "name": "Return OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [1910, 380]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Normalize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize": {
      "main": [
        [
          {
            "node": "Dedupe Get",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedupe Get": {
      "main": [
        [
          {
            "node": "Já enviou?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Já enviou?": {
      "main": [
        [
          {
            "node": "Return Ignored",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Notifish",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Notifish": {
      "main": [
        [
          {
            "node": "Dedupe Set",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedupe Set": {
      "main": [
        [
          {
            "node": "Log Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Success": {
      "main": [
        [
          {
            "node": "Return OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false
}

Observações importantes (produção)

1) Dedupe com Data Store não tem TTL nativo

Ele salva “para sempre” se você não limpar. Em produção, o ideal é:

  • usar Redis para TTL real, ou
  • incluir timestamp e limpar por job diário

Se você quiser, eu te mando a versão com Redis (TTL real).

2) Log endpoint

Se você não tiver endpoint pronto, você pode logar em:

ou até em um arquivo via API sua

PostgreSQL/MySQL via node de banco

Google Sheets (rápido)