Toda automação que recebe webhook — de WhatsApp, de gateway de pagamento, do que for — vai um dia receber a mesma notificação duas vezes. Não é falha da plataforma. É o comportamento documentado: se o seu servidor não confirma o recebimento a tempo, ou confirma e a confirmação se perde no caminho, a plataforma do outro lado reenvia. Ela prefere o risco de duplicar a correr o risco de nunca entregar.

A documentação oficial de webhooks da WhatsApp Cloud API é direta sobre isso: notificações que não são confirmadas continuam sendo reenviadas até que a entrega tenha sucesso, e como o reenvio pode alcançar mais de um app inscrito no mesmo número, o resultado prático é que o mesmo evento chega mais de uma vez na sua ponta. Isso não é uma falha rara em dia ruim de rede. É o modo normal de operação de qualquer sistema que prefere "entregar de novo" a "arriscar não entregar nunca".

Por que isso quebra automação e não quebra em teste

No teste manual você dispara o fluxo uma vez, olha o resultado, segue em frente. Duplicata nunca aparece, porque quem testou foi você, e você não reenviou nada.

Em produção, a origem do reenvio não é bug seu. É rede lenta, é seu servidor demorando para responder dentro da janela que a plataforma espera, é o próprio ACK que se perde depois que o seu processamento já terminou. A plataforma, do lado dela, não tem como saber que você já processou. Ela só sabe que não recebeu confirmação, então reenvia.

Se o seu fluxo trata "webhook chegou" como sinônimo de "evento novo aconteceu", cada reenvio dispara a ação de novo. Numa automação de atendimento isso é a mesma mensagem de boas-vindas mandada três vezes. Numa automação de cobrança, é a mesma cobrança criada duas vezes. O segundo caso já não é feio, é dinheiro saindo do bolso errado.

O padrão que resolve isso tem nome e tem trinta anos de estrada

Chama-se idempotência, e a definição formal está na RFC 7231, seção 4.2.2: um método é idempotente quando o efeito pretendido, no servidor, de várias requisições idênticas é o mesmo que o efeito de uma única requisição. PUT e DELETE são idempotentes por definição do protocolo. POST não é — e é exatamente POST que a maioria dos webhooks usa para te avisar que algo aconteceu.

Como o protocolo não garante idempotência de POST de graça, quem constrói o sistema tem que garantir na aplicação. É o que a Stripe documenta na própria API: toda requisição que cria ou altera algo aceita uma chave de idempotência gerada pelo cliente. Se a mesma chave aparecer de novo — porque a conexão caiu e o cliente reenviou, ou porque o webhook foi entregue duas vezes — a Stripe devolve o resultado da primeira tentativa em vez de repetir a operação. A confiabilidade de reenviar sem medo vem de tratar a repetição na aplicação, não de confiar que a rede vai ser perfeita.

Como isso vira código num fluxo de WhatsApp

Você não precisa do sistema da Stripe inteiro. Precisa de uma chave de deduplicação e um lugar rápido para lembrar quais chaves você já viu.

Toda notificação da WhatsApp Cloud API carrega um identificador único por mensagem (o id dentro de messages[], ou o id dentro de statuses[] para atualização de status). Esse id é a sua chave de idempotência pronta, sem precisar inventar nada:

1. Webhook chega.
2. Extrai o id da mensagem.
3. Já existe esse id no seu registro de processados (Redis, tabela, cache)?
   → Sim: responde 200 e para. Não repete a ação.
   → Não: grava o id, processa, responde 200.

O ponto que mais gente erra aqui é a ordem: gravar o id como processado antes de rodar a ação, não depois. Se você grava depois e o seu processo cai no meio do caminho, o próximo reenvio encontra o id ausente de novo e repete a ação inteira — o mesmo problema que você estava tentando resolver.

O segundo ponto é o prazo de guarda. Você não precisa lembrar de um id para sempre. Um reenvio chega dentro de uma janela de tempo limitada; guardar os últimos dias já cobre o caso real, e evita que o seu registro de deduplicação cresça sem limite.

A pergunta que eu faço antes de aceitar um webhook como fonte de verdade

Não é "o que esse webhook me avisa". É: "o que o meu fluxo faz se essa mesma notificação chegar de novo daqui a trinta segundos?"

Se a resposta é "processa de novo do mesmo jeito", o fluxo tem um buraco que só vai aparecer em produção, com dado real, no dia em que a rede engasgar — e nesse dia ninguém vai estar olhando o painel de execuções, porque nenhuma delas vai aparecer como erro. Duplicata processada com sucesso não acende luz vermelha em lugar nenhum.

Se você tem uma automação de WhatsApp, cobrança ou qualquer coisa que reage a webhook rodando hoje e não sabe responder essa pergunta, vale a pena descobrir antes que ela responda sozinha. Fala comigo — eu olho o fluxo que já está no ar antes de propor qualquer coisa nova.