<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>webhooks Archives - Leonardo Nascimento | Engenheiro de Software</title>
	<atom:link href="https://leonardonascimento.dev/tag/webhooks/feed/" rel="self" type="application/rss+xml" />
	<link>https://leonardonascimento.dev/tag/webhooks/</link>
	<description>Especializado em backend, APIs e sistemas escaláveis. Experiência em arquitetura de sistemas, integrações, mensageria, performance e aplicações de alta disponibilidade.</description>
	<lastBuildDate>Fri, 30 Jan 2026 02:50:50 +0000</lastBuildDate>
	<language>pt-BR</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://leonardonascimento.dev/wp-content/uploads/2021/05/cropped-programming-32x32.png</url>
	<title>webhooks Archives - Leonardo Nascimento | Engenheiro de Software</title>
	<link>https://leonardonascimento.dev/tag/webhooks/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Sistema completo de cobranças via WhatsApp: boleto, Pix e NF-e na prática</title>
		<link>https://leonardonascimento.dev/blog/sistema-completo-de-cobrancas-via-whatsapp-boleto-pix-e-nf/</link>
					<comments>https://leonardonascimento.dev/blog/sistema-completo-de-cobrancas-via-whatsapp-boleto-pix-e-nf/#respond</comments>
		
		<dc:creator><![CDATA[Leonardo]]></dc:creator>
		<pubDate>Fri, 30 Jan 2026 11:00:00 +0000</pubDate>
				<category><![CDATA[API]]></category>
		<category><![CDATA[Arquitetura]]></category>
		<category><![CDATA[Backend]]></category>
		<category><![CDATA[Dicas & Truques]]></category>
		<category><![CDATA[Laravel]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Produção]]></category>
		<category><![CDATA[Programação]]></category>
		<category><![CDATA[api banco inter]]></category>
		<category><![CDATA[api rest]]></category>
		<category><![CDATA[automação financeira]]></category>
		<category><![CDATA[boleto banco inter]]></category>
		<category><![CDATA[boleto registrado]]></category>
		<category><![CDATA[cache redis]]></category>
		<category><![CDATA[cobrança automatizada]]></category>
		<category><![CDATA[cobrança recorrente]]></category>
		<category><![CDATA[cron laravel]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[github]]></category>
		<category><![CDATA[inadimplência]]></category>
		<category><![CDATA[integração banco inter]]></category>
		<category><![CDATA[laravel]]></category>
		<category><![CDATA[laravel 8]]></category>
		<category><![CDATA[laravel scheduler]]></category>
		<category><![CDATA[ligação automática]]></category>
		<category><![CDATA[nginx]]></category>
		<category><![CDATA[oauth2]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[php-fpm]]></category>
		<category><![CDATA[pix api]]></category>
		<category><![CDATA[pix dinâmico]]></category>
		<category><![CDATA[redis]]></category>
		<category><![CDATA[sistema de cobranças]]></category>
		<category><![CDATA[sms cobrança]]></category>
		<category><![CDATA[webhooks]]></category>
		<category><![CDATA[whatsapp api]]></category>
		<guid isPermaLink="false">https://leonardonascimento.dev/?p=2360</guid>

					<description><![CDATA[<p>Automatizar cobranças nunca foi simples. Entre regras bancárias, vencimentos, Pix, boletos registrados, notificações, inadimplência e conciliação, a maioria dos sistemas acaba virando um emaranhado de scripts difíceis de manter. Neste artigo, vou mostrar como funciona, na prática, um sistema real de cobrança construído em Laravel, integrando Boleto e Pix do Banco Inter, com webhooks, PDFs, [&#8230;]</p>
<p>The post <a href="https://leonardonascimento.dev/blog/sistema-completo-de-cobrancas-via-whatsapp-boleto-pix-e-nf/">Sistema completo de cobranças via WhatsApp: boleto, Pix e NF-e na prática</a> appeared first on <a href="https://leonardonascimento.dev">Leonardo Nascimento | Engenheiro de Software</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p><a href="https://leonardonascimento.dev/blog/fluxo-n8n-api-whatsapp-log-com-dedupe-e-tratamento-de-erro/" type="post" id="2316">Automatizar </a>cobranças nunca foi simples. Entre regras bancárias, vencimentos, Pix, boletos registrados, notificações, inadimplência e conciliação, a maioria dos sistemas acaba virando um emaranhado de scripts difíceis de manter.</p>



<p>Neste artigo, vou mostrar <strong>como funciona, na prática</strong>, um sistema real de cobrança construído em <strong><a href="https://leonardonascimento.dev/categoria/laravel" type="link" id="https://leonardonascimento.dev/categoria/laravel" target="_blank" rel="noreferrer noopener nofollow">Laravel</a></strong>, integrando <strong>Boleto e Pix do Banco Inter</strong>, com <strong>webhooks</strong>, <strong>PDFs</strong>, <strong>crons</strong>, <strong>Docker</strong>, <strong>e-mails</strong>, <strong>WhatsApp</strong>, <strong>SMS</strong> e até <strong>ligações automáticas</strong>.</p>



<p>Todo o conteúdo é baseado em um projeto open source real, usado em produção, disponível no GitHub:</p>



<p>👉 <strong>Repositório:</strong><br><a href="https://github.com/leonardop21/boleto-inter-free">https://github.com/leonardop21/boleto-inter-free</a></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Importante: este não é um “tutorial de Hello World”. É um guia técnico completo, voltado para quem quer <strong>entender arquitetura</strong>, <strong>evitar erros comuns</strong> e <strong>colocar cobrança automática em produção com segurança</strong>.</p>
</blockquote>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-por-que-integrar-diretamente-com-o-banco-inter">Por que integrar diretamente com o Banco Inter?</h2>



<p>O Banco Inter oferece uma <a href="https://leonardonascimento.dev/blog/arquitetura-de-uma-api-rest-em-laravel-preparada-para-producao/" type="post" id="2358">API </a>robusta para PJ, com suporte nativo a:</p>



<ul class="wp-block-list">
<li>Boletos registrados</li>



<li>Pix com QR Code dinâmico</li>



<li>Webhooks de status</li>



<li>Consulta de cobranças</li>



<li>Download de PDFs</li>



<li>Extratos bancários</li>
</ul>



<p>Ao integrar diretamente com o Inter, você elimina intermediários, reduz custos e ganha controle total sobre o fluxo financeiro.</p>



<p>Mas isso vem com desafios:</p>



<ul class="wp-block-list">
<li>Certificados mTLS</li>



<li>OAuth2</li>



<li>Regras rígidas de vencimento</li>



<li>Webhooks que precisam ser idempotentes</li>



<li>PDFs que precisam ser armazenados ou regenerados</li>



<li>Conciliação automática</li>
</ul>



<p>É exatamente isso que o projeto resolve.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-visao-geral-da-arquitetura-do-sistema">Visão geral da arquitetura do sistema</h2>



<p>Antes de entrar em código, é importante entender <strong>como o sistema foi pensado</strong>.</p>



<h3 class="wp-block-heading" id="h-componentes-principais">Componentes principais</h3>



<ul class="wp-block-list">
<li><strong>Laravel 8</strong> como base</li>



<li><strong>MySQL</strong> para dados transacionais</li>



<li><strong>Redis</strong> para cache com tags</li>



<li><strong>Banco Inter API</strong> para cobranças</li>



<li><strong>Notifish</strong> para WhatsApp</li>



<li><strong>SMTP</strong> para e-mails</li>



<li><strong>SMS Dev</strong> para SMS</li>



<li><strong>King SMS (Voicer)</strong> para ligações</li>



<li><strong>Docker + Nginx + PHP-FPM</strong> em produção</li>
</ul>



<p>Nada aqui é experimental. Tudo foi pensado para <strong>rodar 24/7</strong>, com falhas previsíveis e logs rastreáveis.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-por-que-redis-e-obrigatorio-neste-projeto">Por que Redis é obrigatório neste projeto?</h2>



<p>Um erro comum em sistemas de cobrança é <strong>consultar a API do banco o tempo todo</strong>.</p>



<p>Esse projeto evita isso usando <strong>cache agressivo com tags</strong>, por exemplo:</p>



<ul class="wp-block-list">
<li>Cache de cobranças</li>



<li>Cache de clientes</li>



<li>Cache de serviços</li>



<li>Cache de status de boletos e Pix</li>
</ul>



<p>Quando um boleto muda de status (via webhook), o cache relacionado é invalidado.</p>



<p>👉 Sem Redis (ou Memcached), o sistema <strong>não funciona corretamente</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-instalacao-do-projeto-ambiente-local">Instalação do projeto (ambiente local)</h2>



<h3 class="wp-block-heading" id="h-1-clonando-o-repositorio">1. Clonando o repositório</h3>



<pre class="wp-block-code"><code>git clone https://github.com/leonardop21/boleto-inter-free.git
cd boleto-inter-free
composer install
</code></pre>



<h3 class="wp-block-heading" id="h-2-configuracao-inicial">2. Configuração inicial</h3>



<pre class="wp-block-code"><code>cp .env.example .env
php artisan key:generate
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-configurando-o-banco-de-dados-e-cache">Configurando o banco de dados e cache</h2>



<p>No <code>.env</code>:</p>



<pre class="wp-block-code"><code>DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=boleto_inter
DB_USERNAME=user
DB_PASSWORD=pass

CACHE_DRIVER=redis
SESSION_DRIVER=redis
TIME_CACHE_IN_SECONDS=604800
</code></pre>



<p>Depois:</p>



<pre class="wp-block-code"><code>php artisan migrate
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-como-funciona-o-modelo-de-cobranca">Como funciona o modelo de cobrança</h2>



<p>O sistema foi pensado para <strong>cobrança recorrente</strong>, mas funciona também para cobranças pontuais.</p>



<h3 class="wp-block-heading" id="h-entidades-principais">Entidades principais</h3>



<ul class="wp-block-list">
<li><strong>Cliente</strong></li>



<li><strong>Serviço</strong></li>



<li><strong>Cliente x Serviço</strong></li>



<li><strong>Boleto</strong></li>



<li><strong>Pix</strong></li>



<li><strong>NF-e (opcional)</strong></li>
</ul>



<p>O fluxo é simples:</p>



<ol class="wp-block-list">
<li>Você cadastra um cliente</li>



<li>Cadastra um serviço</li>



<li>Vincula o serviço ao cliente</li>



<li>O sistema gera boletos ou Pix automaticamente</li>
</ol>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-integracao-com-o-banco-inter-parte-critica">Integração com o Banco Inter (parte crítica)</h2>



<h3 class="wp-block-heading" id="h-certificados-mtls">Certificados mTLS</h3>



<p>O Inter exige:</p>



<ul class="wp-block-list">
<li>Um arquivo <code>.crt</code></li>



<li>Um arquivo <code>.key</code></li>
</ul>



<p>No <code>.env</code>:</p>



<pre class="wp-block-code"><code>INTER_PATH_CRT=/caminho/inter.crt
INTER_PATH_KEY=/caminho/inter.key
</code></pre>



<h3 class="wp-block-heading" id="h-oauth2">OAuth2</h3>



<pre class="wp-block-code"><code>INTER_CLIENT_ID=seu_client_id
INTER_CLIENT_SECRET=seu_client_secret
INTER_CLIENT_SCOPE="extrato.read boleto-cobranca.read boleto-cobranca.write"
INTER_BASE_URL="https://cdpj.partners.bancointer.com.br/"
</code></pre>



<p>Sem isso, <strong>nenhuma requisição funciona</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-webhooks-o-coracao-da-automacao">Webhooks: o coração da automação</h2>



<p>O sistema registra dois webhooks no Inter:</p>



<ul class="wp-block-list">
<li><strong>Boleto</strong></li>



<li><strong>Pix</strong></li>
</ul>



<pre class="wp-block-code"><code>INTER_WEBHOOK_URL=https://seusite.com/api/inter/webhook/boleto
INTER_WEBHOOK_URL_PIX=https://seusite.com/api/inter/webhook/pix
</code></pre>



<h3 class="wp-block-heading" id="h-o-que-acontece-quando-um-pagamento-e-feito">O que acontece quando um pagamento é feito?</h3>



<ol class="wp-block-list">
<li>O Inter chama o webhook</li>



<li>O sistema valida a assinatura</li>



<li>Atualiza o status da cobrança</li>



<li>Invalida caches relacionados</li>



<li>Registra logs</li>



<li>(Opcional) dispara notificações internas</li>
</ol>



<p>Isso evita:</p>



<ul class="wp-block-list">
<li>Jobs de polling</li>



<li>Consultas desnecessárias</li>



<li>Status desatualizado</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-geracao-automatica-de-boletos">Geração automática de boletos</h2>



<p>Este é um dos pontos mais sofisticados do projeto.</p>



<p>Comando:</p>



<pre class="wp-block-code"><code>php artisan ln:auto_generate_boleto
</code></pre>



<h3 class="wp-block-heading" id="h-regras-implementadas">Regras implementadas</h3>



<ul class="wp-block-list">
<li>Se o dia de vencimento já passou → gera para o mês seguinte</li>



<li>Se o cliente escolheu dia 31 → ajusta para último dia do mês</li>



<li>Respeita meses com 28, 29, 30 ou 31 dias</li>



<li>Evita boletos duplicados</li>



<li>Gera PDF automaticamente</li>



<li>Salva logs detalhados</li>
</ul>



<p>Tudo isso roda <strong>sem intervenção humana</strong>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-geracao-automatica-de-pix">Geração automática de Pix</h2>



<p>Comando:</p>



<pre class="wp-block-code"><code>php artisan ln:auto_generate_pix
</code></pre>



<ul class="wp-block-list">
<li>Gera QR Code</li>



<li>Gera payload Pix</li>



<li>Cria cobrança no Inter</li>



<li>Registra vencimento</li>



<li>Gera imagem do QR Code (GD)</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-envio-de-cobrancas-por-e-mail">Envio de cobranças por e-mail</h2>



<p>O sistema envia:</p>



<ul class="wp-block-list">
<li>Boleto em PDF</li>



<li>Pix com QR Code</li>



<li>NF-e (se configurado)</li>
</ul>



<p>Configuração SMTP no <code>.env</code>:</p>



<pre class="wp-block-code"><code>MAIL_HOST=
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
</code></pre>



<p>Além disso, o layout do e-mail é personalizável:</p>



<ul class="wp-block-list">
<li>Logo</li>



<li>Cor de fundo</li>



<li>Links de suporte</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-envio-por-whatsapp-notifish">Envio por WhatsApp (Notifish)</h2>



<p>Integração direta com a API do <a href="https://notifish.com/">Notifish</a>.</p>



<pre class="wp-block-code"><code>NOTIFISH_BASE_URL=https://seu.notifish.com/api/v2/
NOTIFISH_API_KEY=sua_key
NOTIFISH_UUID=sua_instancia
</code></pre>



<p>O sistema gera uma <strong>URL assinada temporária</strong> para o PDF do boleto, válida por poucos minutos — sem expor IDs internos.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-sms-e-ligacoes-automaticas-para-inadimplentes">SMS e ligações automáticas para inadimplentes</h2>



<h3 class="wp-block-heading" id="h-sms">SMS</h3>



<pre class="wp-block-code"><code>php artisan ln:send-boleto-sms
php artisan ln:send-pix-sms
</code></pre>



<h3 class="wp-block-heading" id="h-ligacao-voz-robotica">Ligação (voz robótica)</h3>



<pre class="wp-block-code"><code>php artisan ln:send-boleto-voicer
php artisan ln:send-pix-voicer
</code></pre>



<p>Isso é usado <strong>somente após o vencimento</strong>, como camada extra de cobrança.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-crons-como-tudo-roda-sozinho">Crons: como tudo roda sozinho</h2>



<p>Você <strong>não agenda cada comando individualmente</strong>.</p>



<p>Apenas:</p>



<pre class="wp-block-code"><code>* * * * * php artisan schedule:run
</code></pre>



<p>O Laravel Scheduler cuida do resto.</p>



<p>Exemplo:</p>



<ul class="wp-block-list">
<li>Dia 1 → gera boletos</li>



<li>Antes do vencimento → envia lembrete</li>



<li>Após vencimento → cobrança progressiva</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-docker-em-producao">Docker em produção</h2>



<p>O projeto já vem com:</p>



<ul class="wp-block-list">
<li><code>Dockerfile</code></li>



<li><code>docker-compose.prod.yml</code></li>



<li>Nginx configurado</li>



<li>PHP-FPM otimizado</li>



<li>Redis</li>



<li>MySQL</li>
</ul>



<p>Subir produção:</p>



<pre class="wp-block-code"><code>docker compose -f docker-compose.prod.yml up -d --build
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-logs-rastreabilidade-e-auditoria">Logs, rastreabilidade e auditoria</h2>



<p>Cada processo tem seu próprio log:</p>



<ul class="wp-block-list">
<li>Boletos</li>



<li>Pix</li>



<li>NF-e</li>



<li>Webhooks</li>



<li>Erros de API</li>
</ul>



<p>Isso é fundamental para:</p>



<ul class="wp-block-list">
<li>Suporte</li>



<li>Auditoria</li>



<li>Contabilidade</li>



<li>Diagnóstico rápido</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-para-quem-este-projeto-e-indicado">Para quem este projeto é indicado?</h2>



<ul class="wp-block-list">
<li>Desenvolvedores PHP/Laravel</li>



<li>SaaS com cobrança recorrente</li>



<li>Sistemas internos de empresas</li>



<li>Agências que querem padronizar cobrança</li>



<li>Projetos que precisam de autonomia financeira</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading" id="h-conclusao">Conclusão</h2>



<p>Automatizar cobranças não é só “gerar boleto”.<br>É lidar com:</p>



<ul class="wp-block-list">
<li>Datas</li>



<li>Erros bancários</li>



<li>Comunicação</li>



<li>Inadimplência</li>



<li>Escala</li>



<li>Observabilidade</li>
</ul>



<p>Este projeto resolve isso de forma <strong>pragmática</strong>, <strong>realista</strong> e <strong>testada em produção</strong>.</p>



<p>👉 Repositório completo:<br><a href="https://github.com/leonardop21/boleto-inter-free">https://github.com/leonardop21/boleto-inter-free</a></p>



<p></p>
<p>The post <a href="https://leonardonascimento.dev/blog/sistema-completo-de-cobrancas-via-whatsapp-boleto-pix-e-nf/">Sistema completo de cobranças via WhatsApp: boleto, Pix e NF-e na prática</a> appeared first on <a href="https://leonardonascimento.dev">Leonardo Nascimento | Engenheiro de Software</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://leonardonascimento.dev/blog/sistema-completo-de-cobrancas-via-whatsapp-boleto-pix-e-nf/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Fluxo n8n: API, WhatsApp, Log (com dedupe e tratamento de erro)</title>
		<link>https://leonardonascimento.dev/blog/fluxo-n8n-api-whatsapp-log-com-dedupe-e-tratamento-de-erro/</link>
					<comments>https://leonardonascimento.dev/blog/fluxo-n8n-api-whatsapp-log-com-dedupe-e-tratamento-de-erro/#respond</comments>
		
		<dc:creator><![CDATA[Leonardo]]></dc:creator>
		<pubDate>Tue, 20 Jan 2026 22:30:00 +0000</pubDate>
				<category><![CDATA[API]]></category>
		<category><![CDATA[Programação]]></category>
		<category><![CDATA[automação]]></category>
		<category><![CDATA[integrações]]></category>
		<category><![CDATA[logs]]></category>
		<category><![CDATA[n8n]]></category>
		<category><![CDATA[webhooks]]></category>
		<category><![CDATA[WhatsApp]]></category>
		<guid isPermaLink="false">https://leonardonascimento.dev/?p=2316</guid>

					<description><![CDATA[<p>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: Pré-requisitos 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: [&#8230;]</p>
<p>The post <a href="https://leonardonascimento.dev/blog/fluxo-n8n-api-whatsapp-log-com-dedupe-e-tratamento-de-erro/">Fluxo n8n: API, WhatsApp, Log (com dedupe e tratamento de erro)</a> appeared first on <a href="https://leonardonascimento.dev">Leonardo Nascimento | Engenheiro de Software</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading" id="h-o-cenario-realista">O cenário (realista)</h2>



<p>Você quer receber um evento via API (Webhook), enviar uma mensagem no WhatsApp via <a href="https://notifish.com/" type="link" id="https://notifish.com/" target="_blank" rel="noreferrer noopener nofollow">Notifish </a>e registrar o resultado (sucesso/erro) em log.</p>



<p>Esse fluxo é útil para:</p>



<ul class="wp-block-list">
<li>alertas operacionais</li>



<li>notificações de deploy</li>



<li>falhas de integração</li>



<li>notificações de sistema para grupos</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Pré-requisitos</h2>



<ol class="wp-block-list">
<li><a href="https://leonardonascimento.dev/tag/n8n/" type="post_tag" id="267">n8n rodando e acessível</a></li>



<li>endpoint do Notifish (o seu padrão):<br><code>POST https://meu-dominio.notifish.com/api/v2/{INSTANCE_UUID}/whatsapp/message/groups</code><br>com header <code>Authorization: Bearer ...</code></li>



<li>um lugar para log<br>Vou te dar 2 opções:</li>
</ol>



<ul class="wp-block-list">
<li><strong>A)</strong> log em arquivo via HTTP (endpoint seu) — mais profissional</li>



<li><strong>B)</strong> log no próprio n8n (Execution data) + uma planilha/banco (se preferir)</li>
</ul>



<p>Aqui vou usar a opção <strong>A</strong> (mais “produção”).</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">Parte 1 — Contrato da API (Webhook)</h1>



<p>Você vai expor um endpoint no <a href="https://leonardonascimento.dev/blog/n8n-o-que-e-como-funciona-e-quando-faz-sentido-usar/" type="post" id="2313">n8n</a>, por exemplo:</p>



<pre class="wp-block-code"><code>POST https://SEU_N8N/webhook/notify
</code></pre>



<p>Payload recomendado (simples e suficiente):</p>



<pre class="wp-block-code"><code>{
  "title": "API 5xx alto",
  "message": "Erros 5xx acima do normal em /api/orders",
  "url": "https://seu-dashboard",
  "identifier": "prod:api:5xx",
  "delayMessage": 0
}
</code></pre>



<p>Repare que já mandamos <code>identifier</code>. Isso permite deduplicar e evitar spam.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">Parte 2 — Dedupe (anti-spam) usando Redis (opcional, mas recomendado)</h1>



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



<ul class="wp-block-list">
<li>chave: <code>dedupe:{identifier}</code></li>



<li>TTL: 300s (5 minutos) por exemplo<br>Se existir, você não envia.</li>
</ul>



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



<p>Vou te passar o fluxo com <strong>Data Store do n8n</strong> (não depende de infra extra).</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">Parte 3 — Enviar WhatsApp via Notifish</h1>



<p>O <a href="https://notifish.com/" target="_blank" rel="noreferrer noopener nofollow">Notifish </a>recebe:</p>



<pre class="wp-block-code"><code>{
  "message": "...",
  "identifier": "...",
  "link": true,
  "typing": "composing",
  "delayMessage": 1200
}
</code></pre>



<p>A gente vai montar isso no n8n.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">Parte 4 — Registrar Log</h1>



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



<pre class="wp-block-code"><code>POST https://seu-sistema.com/api/logs/notifish
</code></pre>



<p>Log recomendado:</p>



<pre class="wp-block-code"><code>{
  "identifier": "prod:api:5xx",
  "status": "success",
  "http_status": 200,
  "provider": "notifish",
  "payload": { ... },
  "response": { ... },
  "created_at": "2026-01-22T00:00:00-03:00"
}
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">NODES DO WORKFLOW (passo a passo)</h1>



<h2 class="wp-block-heading">1) Webhook (Trigger)</h2>



<ul class="wp-block-list">
<li>Method: <strong>POST</strong></li>



<li>Path: <strong>notify</strong></li>



<li>Response: JSON (200)</li>
</ul>



<h2 class="wp-block-heading">2) Function (Normalize)</h2>



<p>Cria um formato consistente e monta a mensagem final do WhatsApp:</p>



<ul class="wp-block-list">
<li>título + mensagem + url</li>



<li>garante identifier</li>



<li>define delayMessage</li>
</ul>



<h2 class="wp-block-heading">3) Data Store (Get)</h2>



<p>Busca <code>identifier</code> na datastore (dedupe).</p>



<h2 class="wp-block-heading">4) IF (Já enviou?)</h2>



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



<h2 class="wp-block-heading">5) HTTP Request (Notifish)</h2>



<p>POST no endpoint do Notifish com Bearer Token.</p>



<h2 class="wp-block-heading">6) Data Store (Set)</h2>



<p>Salva <code>identifier</code> com TTL (ex.: 300s) para dedupe.</p>



<h2 class="wp-block-heading">7) HTTP Request (Log Success)</h2>



<p>Envia log para seu endpoint.</p>



<h2 class="wp-block-heading">8) Error Trigger / Catch (Log Error)</h2>



<p>Se Notifish falhar, manda log com status <code>error</code>.</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h1 class="wp-block-heading">JSON do workflow para importar no n8n</h1>



<p>Abaixo um workflow base. Você só precisa trocar:</p>



<ul class="wp-block-list">
<li><code>NOTIFISH_INSTANCE_UUID</code></li>



<li><code>NOTIFISH_TOKEN</code></li>



<li><code>LOG_ENDPOINT_URL</code></li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Import: n8n → Workflows → Import from file/clipboard</p>
</blockquote>



<pre class="wp-block-code"><code>{
  "name": "API → Notifish WhatsApp → Log (com dedupe)",
  "nodes": &#91;
    {
      "parameters": {
        "path": "notify",
        "httpMethod": "POST",
        "responseMode": "lastNode",
        "options": {}
      },
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": &#91;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 &#91;{\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": &#91;520, 300]
    },
    {
      "parameters": {
        "operation": "get",
        "key": "={{$json.identifier}}"
      },
      "name": "Dedupe Get",
      "type": "n8n-nodes-base.dataStore",
      "typeVersion": 1,
      "position": &#91;760, 300]
    },
    {
      "parameters": {
        "conditions": {
          "boolean": &#91;
            {
              "value1": "={{$json.value !== undefined &amp;&amp; $json.value !== null}}",
              "value2": true
            }
          ]
        }
      },
      "name": "Já enviou?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": &#91;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": &#91;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": &#91;
            {
              "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": &#91;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": &#91;1450, 380]
    },
    {
      "parameters": {
        "url": "LOG_ENDPOINT_URL",
        "method": "POST",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": &#91;
            {
              "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": &#91;1690, 380]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\"status\":\"sent\",\"identifier\":\"{{$json.identifier}}\"}",
        "options": {}
      },
      "name": "Return OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": &#91;1910, 380]
    }
  ],
  "connections": {
    "Webhook": {
      "main": &#91;
        &#91;
          {
            "node": "Normalize",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize": {
      "main": &#91;
        &#91;
          {
            "node": "Dedupe Get",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedupe Get": {
      "main": &#91;
        &#91;
          {
            "node": "Já enviou?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Já enviou?": {
      "main": &#91;
        &#91;
          {
            "node": "Return Ignored",
            "type": "main",
            "index": 0
          }
        ],
        &#91;
          {
            "node": "Send Notifish",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Notifish": {
      "main": &#91;
        &#91;
          {
            "node": "Dedupe Set",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedupe Set": {
      "main": &#91;
        &#91;
          {
            "node": "Log Success",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Success": {
      "main": &#91;
        &#91;
          {
            "node": "Return OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false
}
</code></pre>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Observações importantes (produção)</h2>



<h3 class="wp-block-heading">1) Dedupe com Data Store não tem TTL nativo</h3>



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



<ul class="wp-block-list">
<li>usar Redis para TTL real, ou</li>



<li>incluir timestamp e limpar por job diário</li>
</ul>



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



<h3 class="wp-block-heading">2) Log endpoint</h3>



<p>Se você não tiver endpoint pronto, você pode logar em:</p>



<p id="h-">ou até em um arquivo via API sua</p>



<p>PostgreSQL/MySQL via node de banco</p>



<p>Google Sheets (rápido)</p>



<p></p>
<p>The post <a href="https://leonardonascimento.dev/blog/fluxo-n8n-api-whatsapp-log-com-dedupe-e-tratamento-de-erro/">Fluxo n8n: API, WhatsApp, Log (com dedupe e tratamento de erro)</a> appeared first on <a href="https://leonardonascimento.dev">Leonardo Nascimento | Engenheiro de Software</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://leonardonascimento.dev/blog/fluxo-n8n-api-whatsapp-log-com-dedupe-e-tratamento-de-erro/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
