---
title: "Webhook en producción: firma, reintentos e idempotencia"
description: "Tu webhook funciona en la prueba y falla en producción. Los tres problemas que solo aparecen con volumen: mensaje procesado dos veces, evento fuera de orden y endpoint abierto a cualquiera. Con código para cada uno."
url: "https://api-wa.me/es/blog/webhook-produccion-firma-reintento-idempotencia"
language: "es"
og:type: "article"
og:site_name: "WAME API"
---

[Inicio](https://api-wa.me/es)/[Blog de la API de WhatsApp](https://api-wa.me/es/blog)/Webhook en producción: firma, reentrega e idempotencia (lo que nadie prueba antes)

Raphael Serafim· Publicado el 10 de septiembre de 2026· 9 min de lectura

Compartir

# Webhook en producción: firma, reentrega e idempotencia (lo que nadie prueba antes)

Tu webhook funciona en la prueba y falla en producción. Los tres problemas que solo aparecen con volumen: mensaje procesado dos veces, evento fuera de orden y endpoint abierto a cualquiera. Con código para cada uno.

Copiar para LLM[Ver como Markdown](https://api-wa.me/es/blog/webhook-produccion-firma-reintento-idempotencia.md)

**Todo webhook funciona en la primera prueba.** Envías un mensaje, el evento llega, el código responde. Lo que falla es el webhook en producción, con volumen — y falla de tres formas que la prueba manual nunca muestra.

Si tu problema es que el evento **no llega**, el camino es otro: [las 7 causas y cómo probarlo](https://api-wa.me/es/blog/webhook-whatsapp-no-llega-como-probar). Acá el evento llega. El problema es lo que pasa después.

## Problema 1 — El mismo evento procesado dos veces

### Por qué pasa

La plataforma reenvía cuando no recibe `200` rápido. **Eso es correcto**: sin reentrega, un evento se perdería cada vez que tu servidor se reinicia. El comportamiento existe para que no pierdas mensajes.

El efecto colateral es que el mismo evento puede llegar dos o tres veces. Y entonces:

- el bot responde lo mismo tres veces;
- el CRM crea tres registros del mismo contacto;
- el cobro se emite dos veces.

### La corrección, en dos capas

**Capa 1 — responder 200 antes de procesar.** Resuelve la mayoría de los casos en el origen:

javascript

Copiar

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

**Capa 2 — idempotencia.** La capa 1 reduce el duplicado; no lo elimina. La red falla, y una respuesta puede perderse en el camino después de que tu servidor ya procesó. La garantía real es tratar el `messageId` como clave única:

javascript

Copiar

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

  // SETNX con expiración: escribe solo si todavía no existe.
  // La operación es atómica, así que dos entregas simultáneas no
  // pasan las dos — que es el caso que un "if (existe)" seguido
  // de "escribe" deja escapar.
  const inedito = await redis.set(`wh:${msg.id}`, '1', {
    NX: true,
    EX: 60 * 60 * 24,
  });
  if (!inedito) return;   // ya procesamos este evento

  await responderCliente(msg);
}
```

Sin Redis, la misma idea con una tabla y una restricción de unicidad en `message_id` funciona igual: intenta insertar, y si viola la restricción, ignora el evento.

**El orden importa.** Guarda la marca **antes** de actuar, no después. Marcarla después deja la ventana abierta justo en el intervalo en que ocurre el procesamiento, que es cuando suele llegar la reentrega.

## Problema 2 — Endpoint abierto a cualquiera

Si tu URL de webhook es `https://api.tuempresa.com/webhook/whatsapp`, cualquiera que la descubra puede publicar un JSON falso. Según lo que haga tu handler, eso es un mensaje enviado en nombre de tu cliente o un registro falso en la base.

### Capa 1: ruta secreta

El mínimo aceptable, y lleva un minuto:

Copiar

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

El secreto está en la ruta. No es criptografía, pero elimina al escáner automático — y es infinitamente mejor que `/webhook`.

### Capa 2: validar la firma

Cuando la plataforma firma el cuerpo, verifica la firma. Y verifícala bien:

javascript

Copiar

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

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

  const esperada = 'sha256=' + crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    // El cuerpo CRUDO, no el objeto reserializado: JSON.stringify
    // puede reordenar claves y cambiar espaciado, y el hash nunca coincide.
    .update(req.rawBody)
    .digest('hex');

  // Comparación en tiempo constante: `===` filtra información por el
  // tiempo de respuesta y permite descubrir la firma byte a byte.
  const a = Buffer.from(recibida);
  const b = Buffer.from(esperada);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

Para tener `req.rawBody` en Express:

javascript

Copiar

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

### Capa 3: un secreto por cliente

Si entregas sistemas a varios clientes, no uses el mismo secreto para todos. Un secreto por instancia, guardado junto al registro del cliente: si se filtra uno, rotas ese, no los treinta.

## Problema 3 — Eventos fuera de orden

No hay garantía de orden. Dos mensajes enviados en secuencia pueden llegar invertidos, y una reentrega puede poner un evento viejo después de uno nuevo.

Eso rompe una lógica del tipo "el último mensaje define el estado de la conversación":

javascript

Copiar

```
// frágil: depende del orden de llegada
conversacion.ultimoMensaje = msg.text.body;

// robusto: el evento más nuevo gana, llegue cuando llegue
if (!conversacion.ultimoTs || msg.timestamp > conversacion.ultimoTs) {
  conversacion.ultimoMensaje = msg.text.body;
  conversacion.ultimoTs = msg.timestamp;
}
```

Si tu máquina de estados depende de la secuencia, ordena por `timestamp` del evento. Nunca por el orden en que tu servidor lo recibió.

## La arquitectura que resuelve los tres de una vez

javascript

Copiar

```
app.post('/webhook/wame/:secreto', async (req, res) => {
  // 1. autenticación, antes de cualquier trabajo
  if (req.params.secreto !== process.env.WEBHOOK_PATH_SECRET) {
    return res.sendStatus(404);   // 404 y no 403: no confirmes que existe
  }
  if (!firmaValida(req)) return res.sendStatus(401);

  // 2. confirma al instante
  res.sendStatus(200);

  // 3. encola; el procesamiento ocurre fuera de la petición
  await cola.add('webhook', req.body, {
    // el propio id del evento como clave: la cola descarta el
    // duplicado antes incluso de que el worker despierte
    jobId: req.body?.entry?.[0]?.changes?.[0]?.value?.messages?.[0]?.id,
  });
});
```

Con BullMQ, el `jobId` ya da la idempotencia gratis — un job con id repetido se descarta.

## El checklist antes de publicar

- [ ]  Responde `200` antes de procesar
- [ ]  `messageId` guardado como clave única, **antes** de actuar
- [ ]  Ruta del webhook con secreto
- [ ]  Firma validada sobre el cuerpo crudo, con comparación en tiempo constante
- [ ]  Secreto por instancia, no global
- [ ]  Estado resuelto por `timestamp`, no por orden de llegada
- [ ]  Estado de entrega tratado aparte del mensaje recibido
- [ ]  Un error en el procesamiento no tumba la respuesta del endpoint

## Por qué esto es más simple con un solo webhook

Cada punto de arriba es trabajo por integración. Con tres canales en tres plataformas distintas, es tres veces todo: tres validaciones de firma, tres formatos de id, tres lugares donde equivocarse.

Como los tres canales llegan [en el mismo sobre](https://api-wa.me/es/blog/api-instagram-messenger-misma-api-whatsapp), el checklist se escribe una vez y vale para los tres — el campo `provider` dice el canal y nada más cambia.

### ¿Listo para automatizar tu WhatsApp?

Crea tu cuenta gratis y empieza a enviar mensajes por la API en minutos.

[Empezar gratis](https://portal.api-wa.me/sign-up)

## Preguntas frecuentes

¿Por qué el mismo webhook llega dos veces?+

Porque tu endpoint no confirmó el recibo a tiempo. Toda plataforma de webhooks reenvía cuando no recibe un 200 rápido, y reenviar es el comportamiento correcto: es lo que garantiza que un evento no se pierda si tu servidor se cae. Lo que te toca a ti es hacer el procesamiento idempotente.

¿Qué es la idempotencia en un webhook?+

Es la propiedad de procesar el mismo evento varias veces con el mismo resultado final. En la práctica: guarda el identificador del mensaje antes de actuar e ignora el evento si ya fue visto. Sin eso, una reentrega se convierte en cobro doble, respuesta duplicada o dos registros en el CRM.

¿Cómo protejo el endpoint del webhook?+

Tres capas, de la más simple a la más fuerte: un token secreto en la ruta de la URL, validación de firma sobre el cuerpo de la petición cuando la plataforma envía una, y restricción por origen. El mínimo aceptable es la URL secreta; un endpoint con ruta previsible y sin verificación acepta eventos falsos de cualquiera.

¿Debo procesar el webhook de forma asíncrona?+

Sí, siempre que el procesamiento pase de unos milisegundos. Responde 200 de inmediato, mete el evento en una cola y procésalo fuera del ciclo de la petición. Eso resuelve la reentrega por timeout en el origen y además hace que tu endpoint sobreviva a los picos.

¿Los eventos llegan en orden?+

No hay garantía. Dos mensajes enviados en secuencia pueden llegar en orden inverso, y un estado de entrega puede llegar antes que el propio mensaje en escenarios de reentrega. Si el orden importa para tu lógica, ordena por el timestamp del evento, no por el orden de llegada.

## Sigue leyendo

[### API de Instagram y Messenger en la misma API de WhatsApp: una instancia, un estándar

Tres canales oficiales de Meta casi siempre significan tres integraciones. Con una sola instancia, el mismo envío y el mismo sobre de webhook, solo cambia un campo: provider. Cómo funciona y qué te ahorra.](https://api-wa.me/es/blog/api-instagram-messenger-misma-api-whatsapp)[### API no oficial de WhatsApp: qué es, si es segura y cómo usarla (2026)

Entiende qué es una API no oficial de WhatsApp, cómo funciona la conexión por código QR, si es segura y legal, cuál es el riesgo real de bloqueo y cuándo conviene usarla en lugar de la API oficial.](https://api-wa.me/es/blog/api-no-oficial-whatsapp-que-es)[### API de WhatsApp gratis: cómo probarla y empezar sin costo (2026)

¿Existe una API de WhatsApp gratis? Qué se puede hacer sin pagar, cómo probar la integración sin costo, las limitaciones reales de lo gratuito y cómo hacer el primer envío en minutos.](https://api-wa.me/es/blog/api-whatsapp-gratis)

[Volver al blog](https://api-wa.me/es/blog)

## Structured data

```json
{
  "@context": "https://schema.org",
  "@type": "WebSite",
  "name": "WAME API",
  "alternateName": "API Oficial y No Oficial de WhatsApp, Instagram y 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": "Socio Oficial de Meta — WhatsApp, Instagram y Messenger en una sola instancia. Desde 2017.",
  "description": "Plataforma brasileña y Socio Oficial de Meta (Meta Business Partner) para las APIs oficiales de WhatsApp (Cloud API), Instagram Direct y Messenger — las tres en una única instancia, con los mismos endpoints y un único formato de webhook. También ofrece la API no oficial vía Código QR, en la misma plataforma. En el mercado desde 2017, con más de 50 mil instancias creadas, 99,9% de uptime y soporte humano 24/7. SDKs oficiales para Node.js/TypeScript y 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 en producción: firma, reentrega e idempotencia (lo que nadie prueba antes)",
  "description": "Tu webhook funciona en la prueba y falla en producción. Los tres problemas que solo aparecen con volumen: mensaje procesado dos veces, evento fuera de orden y endpoint abierto a cualquiera. Con código para cada uno.",
  "image": "https://api-wa.me/es/blog/webhook-produccion-firma-reintento-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/es/blog/webhook-produccion-firma-reintento-idempotencia"
  },
  "keywords": "webhook idempotencia, webhook duplicado whatsapp, validar firma webhook, webhook reintentos, seguridad webhook whatsapp, cola webhook whatsapp",
  "inLanguage": "es"
}
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "position": 1,
      "name": "Home",
      "item": "https://api-wa.me"
    },
    {
      "@type": "ListItem",
      "position": 2,
      "name": "Blog de la API de WhatsApp",
      "item": "https://api-wa.me/blog"
    },
    {
      "@type": "ListItem",
      "position": 3,
      "name": "Webhook en producción: firma, reentrega e idempotencia (lo que nadie prueba antes)",
      "item": "https://api-wa.me/es/blog/webhook-produccion-firma-reintento-idempotencia"
    }
  ]
}
{
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": [
    {
      "@type": "Question",
      "name": "¿Por qué el mismo webhook llega dos veces?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Porque tu endpoint no confirmó el recibo a tiempo. Toda plataforma de webhooks reenvía cuando no recibe un 200 rápido, y reenviar es el comportamiento correcto: es lo que garantiza que un evento no se pierda si tu servidor se cae. Lo que te toca a ti es hacer el procesamiento idempotente."
      }
    },
    {
      "@type": "Question",
      "name": "¿Qué es la idempotencia en un webhook?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Es la propiedad de procesar el mismo evento varias veces con el mismo resultado final. En la práctica: guarda el identificador del mensaje antes de actuar e ignora el evento si ya fue visto. Sin eso, una reentrega se convierte en cobro doble, respuesta duplicada o dos registros en el CRM."
      }
    },
    {
      "@type": "Question",
      "name": "¿Cómo protejo el endpoint del webhook?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Tres capas, de la más simple a la más fuerte: un token secreto en la ruta de la URL, validación de firma sobre el cuerpo de la petición cuando la plataforma envía una, y restricción por origen. El mínimo aceptable es la URL secreta; un endpoint con ruta previsible y sin verificación acepta eventos falsos de cualquiera."
      }
    },
    {
      "@type": "Question",
      "name": "¿Debo procesar el webhook de forma asíncrona?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "Sí, siempre que el procesamiento pase de unos milisegundos. Responde 200 de inmediato, mete el evento en una cola y procésalo fuera del ciclo de la petición. Eso resuelve la reentrega por timeout en el origen y además hace que tu endpoint sobreviva a los picos."
      }
    },
    {
      "@type": "Question",
      "name": "¿Los eventos llegan en orden?",
      "acceptedAnswer": {
        "@type": "Answer",
        "text": "No hay garantía. Dos mensajes enviados en secuencia pueden llegar en orden inverso, y un estado de entrega puede llegar antes que el propio mensaje en escenarios de reentrega. Si el orden importa para tu lógica, ordena por el timestamp del evento, no por el orden de llegada."
      }
    }
  ]
}
```
