
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
- n8n rodando e acessível
- endpoint do Notifish (o seu padrão):
POST https://meu-dominio.notifish.com/api/v2/{INSTANCE_UUID}/whatsapp/message/groups
com headerAuthorization: Bearer ... - 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_UUIDNOTIFISH_TOKENLOG_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)







