---
title: "Webhook em produção: assinatura, retry e idempotência"
description: "Seu webhook funciona no teste e falha em produção. Os três problemas que só aparecem com volume: mensagem processada duas vezes, evento fora de ordem e endpoint aberto para qualquer um. Com código para tratar cada um."
url: "https://api-wa.me/blog/webhook-producao-assinatura-retry-idempotencia"
language: "pt-BR"
og:type: "article"
og:site_name: "WAME API"
---

[Início](https://api-wa.me/)/[Blog da API do WhatsApp](https://api-wa.me/blog)/Webhook em produção: assinatura, reentrega e idempotência (o que ninguém testa antes)

Raphael Serafim· Publicado em 10 de setembro de 2026· 9 min de leitura

Compartilhar

# Webhook em produção: assinatura, reentrega e idempotência (o que ninguém testa antes)

Seu webhook funciona no teste e falha em produção. Os três problemas que só aparecem com volume: mensagem processada duas vezes, evento fora de ordem e endpoint aberto para qualquer um. Com código para tratar cada um.

Copiar para LLM[Ver como Markdown](https://api-wa.me/blog/webhook-producao-assinatura-retry-idempotencia.md)

**Todo webhook funciona no primeiro teste.** Você manda uma mensagem, o evento chega, o código responde. O que falha é o webhook em produção, com volume — e falha de três formas que o teste manual nunca mostra.

Se o seu problema é o evento **não chegar**, o caminho é outro: [as 7 causas e como testar](https://api-wa.me/blog/webhook-whatsapp-nao-chega-como-testar). Aqui o evento chega. O problema é o que acontece depois.

## Problema 1 — O mesmo evento processado duas vezes

### Por que acontece

A plataforma reenvia quando não recebe `200` rápido. **Isso é correto**: sem reentrega, um evento se perderia sempre que o seu servidor reiniciasse. O comportamento existe para você não perder mensagem.

O efeito colateral é que o mesmo evento pode chegar duas ou três vezes. E aí:

- o bot responde a mesma coisa três vezes;
- o CRM cria três registros do mesmo contato;
- a cobrança é lançada duas vezes.

### A correção em duas camadas

**Camada 1 — responder 200 antes de processar.** Resolve a maioria dos casos na origem:

```js
app.post('/webhook/wame', (req, res) => {
  res.sendStatus(200);
  fila.add(req.body).catch(console.error);
});
```

**Camada 2 — idempotência.** A camada 1 reduz a duplicata; ela não a elimina. Rede tem falha, e uma resposta pode se perder no caminho depois de o seu servidor já ter processado. A garantia real é tratar o `messageId` como chave única:

```js
async function processar(evento) {
  const msg = evento?.entry?.[0]?.changes?.[0]?.value?.messages?.[0];
  if (!msg) return;

  // SETNX com expiração: grava só se ainda não existe.
  // A operação é atômica, então duas entregas simultâneas
  // não passam as duas — que é o caso que um "if (existe)"
  // seguido de "grava" deixa escapar.
  const inedito = await redis.set(`wh:${msg.id}`, '1', {
    NX: true,
    EX: 60 * 60 * 24,
  });
  if (!inedito) return;   // já processamos este evento

  await responderCliente(msg);
}
```

Sem Redis, a mesma ideia com uma tabela e uma restrição de unicidade em `message_id` funciona igual: tente inserir, e se violar a restrição, ignore o evento.

**Ordem importa.** Grave a marca **antes** de agir, não depois. Marcar depois deixa a janela aberta exatamente no intervalo em que o processamento acontece — que é quando a reentrega costuma chegar.

## Problema 2 — Endpoint aberto para qualquer um

Se a sua URL de webhook é `https://api.suaempresa.com/webhook/whatsapp`, qualquer pessoa que a descubra pode postar um JSON forjado. Dependendo do que o seu handler faz, isso é mensagem enviada em nome do seu cliente ou registro falso no banco.

### Camada 1: caminho secreto

O mínimo aceitável, e leva um minuto:

```
https://api.suaempresa.com/webhook/wame/a8f3d92e4b17c05f
```

O segredo está no caminho. Não é criptografia, mas elimina o varredor automático — e é infinitamente melhor que `/webhook`.

### Camada 2: validar assinatura

Quando a plataforma assina o corpo, confira a assinatura. E confira do jeito certo:

```js
import crypto from 'node:crypto';

function assinaturaValida(req) {
  const recebida = req.get('X-Hub-Signature-256');
  if (!recebida) return false;

  const esperada = 'sha256=' + crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    // O corpo CRU, não o objeto reserializado: JSON.stringify
    // pode reordenar chaves e mudar espaçamento, e aí o hash
    // nunca bate.
    .update(req.rawBody)
    .digest('hex');

  // Comparação em tempo constante: `===` vaza informação pelo
  // tempo de resposta e permite descobrir a assinatura byte a byte.
  const a = Buffer.from(recebida);
  const b = Buffer.from(esperada);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

Para ter `req.rawBody` no Express:

```js
app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; },
}));
```

### Camada 3: um segredo por cliente

Se você [entrega sistemas para vários clientes](https://api-wa.me/api-whatsapp-para-software-house), não use o mesmo segredo para todos. Um segredo por instância, guardado junto do registro do cliente: vazou um, você gira aquele, não os trinta.

## Problema 3 — Eventos fora de ordem

Não há garantia de ordem. Duas mensagens enviadas em sequência podem chegar trocadas, e uma reentrega pode colocar um evento antigo depois de um novo.

Isso quebra lógica do tipo "a última mensagem define o estado da conversa":

```js
// frágil: depende da ordem de chegada
conversa.ultimaMensagem = msg.text.body;

// robusto: o evento mais novo vence, chegue quando chegar
if (!conversa.ultimoTs || msg.timestamp > conversa.ultimoTs) {
  conversa.ultimaMensagem = msg.text.body;
  conversa.ultimoTs = msg.timestamp;
}
```

Se a sua máquina de estados depende de sequência, ordene por `timestamp` do evento. Nunca pela ordem em que o seu servidor recebeu.

## A arquitetura que resolve os três de uma vez

```js
app.post('/webhook/wame/:segredo', async (req, res) => {
  // 1. autenticação, antes de qualquer trabalho
  if (req.params.segredo !== process.env.WEBHOOK_PATH_SECRET) {
    return res.sendStatus(404);   // 404 e não 403: não confirme que existe
  }
  if (!assinaturaValida(req)) return res.sendStatus(401);

  // 2. confirma na hora
  res.sendStatus(200);

  // 3. enfileira; o processamento acontece fora da requisição
  await fila.add('webhook', req.body, {
    // o próprio id do evento como chave: a fila descarta a
    // duplicata antes mesmo de o worker acordar
    jobId: req.body?.entry?.[0]?.changes?.[0]?.value?.messages?.[0]?.id,
  });
});
```

Com BullMQ, o `jobId` já dá a idempotência de graça — job com id repetido é descartado. Sem fila, o `SETNX` do Redis faz o mesmo papel dentro do worker.

## O checklist antes de subir

- [ ]  Responde `200` antes de processar
- [ ]  `messageId` guardado como chave única, **antes** de agir
- [ ]  Caminho do webhook com segredo
- [ ]  Assinatura validada sobre o corpo cru, com comparação em tempo constante
- [ ]  Segredo por instância, não global
- [ ]  Estado resolvido por `timestamp`, não por ordem de chegada
- [ ]  Status de entrega tratado separado de mensagem recebida
- [ ]  Erro no processamento não derruba a resposta do endpoint

## Por que isso fica mais simples com um webhook só

Cada item acima é trabalho por integração. Com três canais em três plataformas diferentes, é três vezes tudo: três validações de assinatura, três formatos de id, três lugares para errar.

Como a WAME entrega [WhatsApp, Instagram e Messenger no mesmo envelope](https://api-wa.me/blog/webhook-whatsapp-instagram-messenger-padrao-meta), o checklist é escrito uma vez e vale para os três — o campo `provider` diz o canal e nada mais muda. É o mesmo motivo pelo qual [o mesmo handler serve todo projeto](https://api-wa.me/blog/api-instagram-messenger-mesma-api-whatsapp) que você entrega.

### Pronto para automatizar seu WhatsApp?

Crie sua conta gratuita e comece a enviar mensagens pela API em minutos.

[Começar grátis](https://portal.api-wa.me/sign-up)

## Perguntas frequentes

Por que o mesmo webhook chega duas vezes?+

Porque o seu endpoint não confirmou o recebimento a tempo. Toda plataforma de webhook reenvia quando não recebe 200 rapidamente, e reenviar é o comportamento correto: é o que garante que um evento não se perca quando o seu servidor cai. O que cabe a você é tornar o processamento idempotente, para que a duplicata não vire efeito duplicado.

O que é idempotência num webhook?+

É a propriedade de processar o mesmo evento várias vezes com o mesmo resultado final. Na prática: guarde o identificador da mensagem antes de agir e ignore o evento se ele já foi visto. Sem isso, uma reentrega vira cobrança dobrada, resposta duplicada ou dois registros no CRM.

Como proteger o endpoint de webhook?+

Três camadas, da mais simples à mais forte: um token secreto no caminho da URL, validação de assinatura no corpo da requisição quando a plataforma envia uma, e restrição por origem. O mínimo aceitável é a URL secreta; endpoint com caminho previsível e sem verificação aceita evento forjado de qualquer um.

Devo processar o webhook de forma assíncrona?+

Sim, sempre que o processamento passar de alguns milissegundos. Responda 200 imediatamente, coloque o evento numa fila e processe fora do ciclo da requisição. Isso resolve a reentrega por timeout na origem e ainda deixa o seu endpoint sobreviver a picos de volume.

Os eventos de webhook chegam em ordem?+

Não há garantia. Duas mensagens enviadas em sequência podem chegar fora de ordem, e o status de entrega pode chegar antes da própria mensagem em cenários de reentrega. Se a ordem importa para a sua lógica, ordene pelo timestamp do evento, não pela ordem de chegada.

## Continue lendo

[### Como criar um chatbot de IA com a API da OpenAI para responder no WhatsApp

Um webhook, uma chamada à API da OpenAI e uma resposta pela WAME API: o código completo de um chatbot de IA que atende no WhatsApp, Instagram e Messenger. Com memória por contato, controle de custo e o que fazer quando a IA não deve responder.](https://api-wa.me/blog/chatbot-ia-openai-whatsapp)[### Cobrança por Pix dentro do WhatsApp pela API: como enviar e o que muda na conversão

Mandar o código Pix no WhatsApp resolve o pior ponto da cobrança digital: o cliente não precisa sair do app. Como enviar a cobrança pela API, tratar a confirmação e evitar os erros que transformam a facilidade em suporte.](https://api-wa.me/blog/cobrar-pix-whatsapp-api)[### Erros da API do WhatsApp: o que cada um significa e como tratar

A mensagem não saiu e o log diz apenas 'erro ao enviar'. Os erros que você vai encontrar de verdade — janela fechada, número inválido, template não aprovado, limite atingido, instância caída — e o tratamento certo para cada um.](https://api-wa.me/blog/erros-api-whatsapp-como-tratar)

[Voltar ao blog](https://api-wa.me/blog)

## Structured data

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "WAME API",
  "alternateName": "API Oficial e Não Oficial de WhatsApp, Instagram e Messenger",
  "url": "https://api-wa.me",
  "inLanguage": "pt-BR",
  "publisher": {
    "@id": "https://api-wa.me/#organization",
    "@type": "Organization",
    "name": "WAME API",
    "url": "https://api-wa.me"
  }
}
{
  "@context": "https://schema.org",
  "@type": "Organization",
  "@id": "https://api-wa.me/#organization",
  "name": "WAME API",
  "alternateName": [
    "WAME",
    "Wame API",
    "wame.api.br",
    "api-wa.me"
  ],
  "url": "https://api-wa.me",
  "logo": {
    "@type": "ImageObject",
    "url": "https://api-wa.me/images/web-app-manifest-512x512.png",
    "width": 512,
    "height": 512
  },
  "disambiguatingDescription": "WAME API é uma empresa brasileira de software, fundada em 2017 e parceira oficial da Meta (Meta Business Partner), que fornece APIs de WhatsApp, Instagram Direct e Messenger. Não tem relação com o wa.me, que é o encurtador de links operado pela WhatsApp LLC.",
  "identifier": {
    "@type": "PropertyValue",
    "propertyID": "INPI-BR",
    "name": "Pedido de registro de marca (INPI, classe NCL 42)",
    "value": "944724159"
  },
  "foundingDate": "2017",
  "slogan": "Parceira Oficial da Meta — WhatsApp, Instagram e Messenger numa instância só. Desde 2017.",
  "description": "Plataforma brasileira e Parceira Oficial da Meta (Meta Business Partner) para as APIs oficiais de WhatsApp (Cloud API), Instagram Direct e Messenger — os três numa única instância, com os mesmos endpoints e um único formato de webhook. Também oferece a API não oficial via QR Code, na mesma plataforma. No mercado desde 2017, com mais de 50 mil instâncias criadas, 99,9% de uptime e suporte humano 24/7 em português. SDKs oficiais para Node.js/TypeScript e PHP.",
  "knowsAbout": [
    "WhatsApp Cloud API oficial (Meta)",
    "API oficial de Instagram (Direct)",
    "API oficial de Messenger",
    "API multicanal Meta",
    "Meta Business Partner",
    "WhatsApp API",
    "API não oficial de WhatsApp",
    "automação de WhatsApp",
    "números virtuais",
    "webhooks"
  ],
  "sameAs": [
    "https://github.com/wame-api",
    "https://www.linkedin.com/company/wameapi",
    "https://www.instagram.com/wame.api/",
    "https://www.youtube.com/@wameapi"
  ],
  "contactPoint": {
    "@type": "ContactPoint",
    "contactType": "customer support",
    "url": "https://api-wa.me/contact",
    "availableLanguage": [
      "Portuguese"
    ]
  }
}
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "headline": "Webhook em produção: assinatura, reentrega e idempotência (o que ninguém testa antes)",
  "description": "Seu webhook funciona no teste e falha em produção. Os três problemas que só aparecem com volume: mensagem processada duas vezes, evento fora de ordem e endpoint aberto para qualquer um. Com código para tratar cada um.",
  "image": "https://api-wa.me/blog/webhook-producao-assinatura-retry-idempotencia/opengraph-image",
  "datePublished": "2026-09-10",
  "dateModified": "2026-09-10",
  "author": {
    "@type": "Person",
    "name": "Raphael Serafim",
    "url": "https://github.com/raphaelvserafim",
    "sameAs": [
      "https://github.com/raphaelvserafim"
    ]
  },
  "publisher": {
    "@type": "Organization",
    "name": "api-wa.me",
    "logo": {
      "@type": "ImageObject",
      "url": "https://api-wa.me/images/screenshot.png"
    }
  },
  "mainEntityOfPage": {
    "@type": "WebPage",
    "@id": "https://api-wa.me/blog/webhook-producao-assinatura-retry-idempotencia"
  },
  "keywords": "webhook idempotência, webhook duplicado whatsapp, validar assinatura webhook, webhook retry, segurança webhook whatsapp, webhook produção api, fila webhook whatsapp",
  "inLanguage": "pt-BR"
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://api-wa.me"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog da API do WhatsApp",
      "item": "https://api-wa.me/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Webhook em produção: assinatura, reentrega e idempotência (o que ninguém testa antes)",
      "item": "https://api-wa.me/blog/webhook-producao-assinatura-retry-idempotencia"
    }
  ]
}
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "Por que o mesmo webhook chega duas vezes?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Porque o seu endpoint não confirmou o recebimento a tempo. Toda plataforma de webhook reenvia quando não recebe 200 rapidamente, e reenviar é o comportamento correto: é o que garante que um evento não se perca quando o seu servidor cai. O que cabe a você é tornar o processamento idempotente, para que a duplicata não vire efeito duplicado."
      }
    },
    {
      "@type": "Question",
      "name": "O que é idempotência num webhook?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "É a propriedade de processar o mesmo evento várias vezes com o mesmo resultado final. Na prática: guarde o identificador da mensagem antes de agir e ignore o evento se ele já foi visto. Sem isso, uma reentrega vira cobrança dobrada, resposta duplicada ou dois registros no CRM."
      }
    },
    {
      "@type": "Question",
      "name": "Como proteger o endpoint de webhook?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Três camadas, da mais simples à mais forte: um token secreto no caminho da URL, validação de assinatura no corpo da requisição quando a plataforma envia uma, e restrição por origem. O mínimo aceitável é a URL secreta; endpoint com caminho previsível e sem verificação aceita evento forjado de qualquer um."
      }
    },
    {
      "@type": "Question",
      "name": "Devo processar o webhook de forma assíncrona?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Sim, sempre que o processamento passar de alguns milissegundos. Responda 200 imediatamente, coloque o evento numa fila e processe fora do ciclo da requisição. Isso resolve a reentrega por timeout na origem e ainda deixa o seu endpoint sobreviver a picos de volume."
      }
    },
    {
      "@type": "Question",
      "name": "Os eventos de webhook chegam em ordem?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Não há garantia. Duas mensagens enviadas em sequência podem chegar fora de ordem, e o status de entrega pode chegar antes da própria mensagem em cenários de reentrega. Se a ordem importa para a sua lógica, ordene pelo timestamp do evento, não pela ordem de chegada."
      }
    }
  ]
}
```
