Módulo 1: De constructor a dueno del sistema

5. Entrega 'al menos una vez' y el problema del duplicado

Descripción

Al terminar esta lección vas a poder explicar por qué el mismo evento te llega dos veces, y por qué eso no es un accidente raro sino la regla con la que están construidos casi todos los sistemas que disparan tus workflows. Vas a conocer la garantía que ofrecen los webhooks, los reintentos y el polling —se llama entrega "al menos una vez"— y por qué su opuesta ideal, "exactamente una vez", es tan difícil de lograr que la industria decidió no perseguirla y resolver el problema por otro lado. Sobre todo, vas a entender que el duplicado no es algo que debas evitar que ocurra, sino algo con lo que debes aprender a convivir.

Esto importa porque cambia por completo la estrategia. Si crees que el duplicado es un fallo excepcional, vas a gastar tu energía tratando de que no pase —y vas a perder, porque no depende de ti—. Si entiendes que el duplicado es inevitable por diseño, vas a gastar tu energía en lo correcto: en hacer que, cuando pase, no haga daño. Esta lección es el giro que reorienta toda la guía, de "cómo evito los duplicados" a "cómo hago que los duplicados sean inofensivos".

Conexión con el módulo: en la lección 4 aprendiste que cada disparo crea una ejecución, y quedó una pregunta suelta: ¿por qué un disparador manda el mismo evento dos veces? Esta lección la contesta. Es la pieza que explica el origen del duplicado, mientras que la lección 4 explicó su mecánica (dos ejecuciones que ambas cobran). Juntas, las dos completan el cuadro: los disparadores entregan al menos una vez (esta lección), y n8n convierte cada entrega en una ejecución que corre los efectos (lección 4). La lección 6 toma esto y lo pone junto a los otros modos de falla; la lección 7 te da la herramienta conceptual —lecturas vs efectos— para saber cuáles duplicados importan.

El repartidor que prefiere entregar de más

Empecemos con una imagen, porque la idea de fondo es más simple de lo que suena.

Imagina un servicio de mensajería que entrega paquetes importantes —documentos legales, digamos— y que tiene una sola regla de oro: nunca, jamás, dejar de entregar un paquete. Perder un documento legal es catastrófico; entregar uno de más es una molestia menor. Dada esa asimetría, ¿cómo se comporta el repartidor?

El repartidor toca la puerta y entrega el paquete. Espera que le firmes el recibo. Si le firmas, perfecto, se va tranquilo. Pero si no le firmas —porque no estabas, porque la conexión con su dispositivo falló, porque tardaste demasiado— el repartidor tiene un problema: no sabe si el paquete llegó o no. Y dada su regla de oro, hace lo único sensato: vuelve mañana y entrega otro paquete. Prefiere que recibas dos documentos idénticos a arriesgarse a que no recibas ninguno.

Desde tu lado, recibiste el documento dos veces. Es molesto. Pero fíjate que el repartidor no cometió ningún error: hizo exactamente lo correcto según su regla de oro. El duplicado no es un bug del repartidor; es la consecuencia lógica de una regla razonable —"mejor de más que de menos"— cuando la confirmación se pierde.

Así funcionan casi todos los sistemas que disparan tus workflows. Un proveedor de webhooks, una pasarela de pago que te notifica, una API que reintenta: todos prefieren mandarte un evento de más a arriesgarse a que se pierda. Y todos, cuando no reciben tu confirmación a tiempo, reenvían. El duplicado que ves no es un accidente; es el precio, aceptado a propósito, de no perder nunca un evento.

Las tres garantías posibles

Para nombrar esto con precisión, la ingeniería de sistemas distribuidos habla de tres niveles de garantía de entrega. Vale la pena conocer los tres, porque el vocabulario aparece en la documentación de casi cualquier servicio que integres.

"Como mucho una vez" (at most once). El sistema te manda el evento una sola vez y no reintenta. Si se pierde en el camino, se perdió: no te enteras. La ventaja es que nunca hay duplicados. La desventaja es que puede haber pérdidas, y para la mayoría de los negocios perder un pedido es peor que procesarlo dos veces. Casi nadie usa esta garantía para cosas importantes.

"Exactamente una vez" (exactly once). El sueño: el evento te llega una vez y solo una, ni se pierde ni se duplica. Es lo que todos quieren. Y es —esta es la parte que sorprende— extraordinariamente difícil de lograr de verdad en un sistema distribuido, por una razón que vamos a ver en un momento. Tan difícil que la mayoría de los servicios ni lo intentan.

"Al menos una vez" (at least once). El evento te llega una vez o más de una, pero nunca se pierde. Es la garantía del repartidor de documentos: prefiere entregar de más antes que perder. Es, con enorme diferencia, la garantía más común en el mundo real, porque combina lo único innegociable (no perder eventos) con una implementación factible. El precio que pagas es que tienes que estar preparado para recibir duplicados.

Aquí está el hecho central de la lección, el que quiero que te grabes:

La inmensa mayoría de los disparadores de tus workflows ofrecen entrega "al menos una vez". Eso significa que recibir el mismo evento dos veces no es la excepción: es una posibilidad garantizada por diseño.

No es que los proveedores sean descuidados. Es que eligieron —correctamente— no perder eventos, y el precio de esa elección es que a veces entregan de más. El duplicado es una característica del contrato, no una falla.

Vale la pena que veas dónde aparece esta garantía por escrito, porque no es un secreto: los proveedores serios la documentan. Si abres la documentación de webhooks de casi cualquier pasarela de pago, plataforma de mensajería o sistema de eventos, vas a encontrar, en algún lugar, una frase parecida a "los eventos se entregan al menos una vez, así que tu endpoint debe ser idempotente" o "prepárate para recibir el mismo evento más de una vez". No lo dicen como disculpa; lo dicen como especificación, porque saben que reenviar es parte de su trabajo y que deduplicar es parte del tuyo. Cuando integres un servicio nuevo, buscar esa frase en su documentación es un buen primer paso: te confirma con qué garantía estás tratando y, muchas veces, te dice qué campo usar para deduplicar.

Por qué "exactamente una vez" es casi imposible

Detengámonos en esto, porque entender por qué no existe el "exactamente una vez" fácil es lo que te convence de dejar de buscarlo.

El problema es el mismo del repartidor, y se reduce a una frase: cuando una confirmación se pierde, el emisor no puede distinguir entre "el mensaje no llegó" y "el mensaje llegó pero la confirmación se perdió". Los dos casos se ven idénticos desde su lado: mandó algo y no recibió respuesta. Ante esa duda, solo tiene dos opciones, y las dos son imperfectas:

  • No reenviar. Si el mensaje de verdad no había llegado, acabas de perderlo. → riesgo de pérdida (at most once).
  • Reenviar. Si el mensaje sí había llegado, acabas de duplicarlo. → riesgo de duplicado (at least once).

No hay una tercera opción mágica, porque el emisor no tiene forma de saber cuál de los dos casos es. La incertidumbre es fundamental: nace de que la confirmación viaja por el mismo canal poco confiable que el mensaje, y ese canal puede fallar en cualquier dirección.

Piénsalo con una llamada telefónica que se corta. Le dictas un número a alguien y la llamada se corta justo cuando terminabas. ¿Anotó el número o no? No lo sabes. Si vuelves a llamar y se lo dictas de nuevo, quizá lo anota dos veces. Si no vuelves a llamar, quizá no lo anotó ninguna. No hay forma, desde tu lado, de saber cuál pasó sin información adicional. Los sistemas distribuidos viven permanentemente en ese momento de "se cortó justo al final".

Entonces, ¿cómo se logra el "exactamente una vez" cuando de verdad se necesita? No eliminando los duplicados en la entrega —eso es imposible— sino aceptando los duplicados en la entrega y descartándolos en la recepción. El emisor manda al menos una vez; el receptor reconoce "este evento ya lo procesé" y ignora las repeticiones. El resultado neto, visto desde afuera, es "exactamente una vez", pero se construye sobre "al menos una vez" más un receptor inteligente.

Y ese receptor inteligente eres tú. Tu workflow. Esta es la revelación que reorienta la guía:

No vas a lograr que los eventos lleguen exactamente una vez —eso no depende de ti—. Vas a lograr que tu workflow trate el segundo evento como lo que es: un duplicado que ya procesó, y que puede ignorar sin hacer daño.

Eso —ignorar con seguridad el duplicado, o hacer que procesarlo de nuevo no cambie nada— es la idempotencia del Módulo 2. Toda la guía es la construcción de ese receptor inteligente.

De dónde vienen los duplicados en order-triage

Bajemos de la teoría al caso concreto. En order-triage, el mismo pedido puede llegar dos veces por varios caminos, y todos son formas de "al menos una vez". Ya nombramos tres en la lección 1; ahora los entiendes de verdad.

El proveedor del webhook reintenta. La tienda en línea —o la pasarela, o el sistema que dispara order-triage— te manda el pedido y espera que tu webhook responda rápido con un "recibido". Si tu instancia tarda, o hay un parpadeo de red, o el workflow demora en responder, el proveedor no recibe tu confirmación a tiempo. Fiel a su garantía "al menos una vez", reenvía. Segundo disparo, segunda ejecución.

El polling se solapa. Algunos workflows no esperan un webhook, sino que preguntan cada cierto tiempo "¿hay pedidos nuevos?". Si una consulta tarda más que el intervalo entre consultas, dos consultas pueden solaparse y traer el mismo pedido. Es otra fuente de duplicado, distinta del webhook pero de la misma familia.

n8n reintenta. Como viste en la lección 4, si un nodo o una ejecución fallan y hay reintento configurado —o alguien reintenta a mano— la acción se ejecuta de nuevo. Aquí el duplicado no viene de afuera sino de tu propio sistema tratando de recuperarse.

El humano hace doble clic. El más humilde y el más común. Un cliente presiona "confirmar", la página tarda, presiona otra vez. Dos disparos, un pedido en su cabeza.

Fíjate en algo: los cuatro caminos son distintos, pero todos producen el mismo resultado —el mismo evento entra dos veces— y todos tienen la misma causa profunda —alguien, en algún lado, prefirió mandar de más a arriesgarse a perder—. Por eso no tiene sentido pelear con cada camino por separado. La defensa correcta no es "evitar que el proveedor reintente" ni "evitar que el humano haga doble clic" —no controlas ninguno de los dos—. La defensa correcta es una sola, aguas abajo: que tu workflow reconozca el duplicado y no lo procese dos veces, venga por donde venga.

El duplicado a escala: por qué "raro" no es "nunca"

Vale la pena hacer una cuenta rápida, porque es la que convierte "esto casi nunca pasa" en "esto pasa seguido", y es la que justifica el esfuerzo de la guía.

Supón —es una hipótesis, tendríamos que medir tu caso real— que solo 1 de cada 500 eventos se entrega dos veces. Suena despreciable: un 0.2%. Con ese número, es fácil concluir "no vale la pena preocuparse".

Ahora multiplícalo por el volumen. Si Cumbre procesa 500 pedidos al día, ese 1 de cada 500 significa, en promedio, un cobro duplicado por día. Trescientos y pico de clientes cobrados de más al año, cada uno con su llamada de reclamo, su reembolso y su golpe a la confianza. Lo que en una ejecución era despreciable, a escala de un día es un incidente diario.

Esta es la misma aritmética que viste en la lección 2 aplicada al fallo parcial, y vale para todos los modos de falla: un evento improbable en una ejecución cualquiera es rutinario cuando hay miles de ejecuciones. Por eso el dueño del sistema no pregunta "¿qué tan probable es en una corrida?" sino "¿cuántas veces al mes, dado mi volumen?". La primera pregunta te hace descartar el riesgo; la segunda te hace dimensionarlo. Y a casi cualquier volumen de producción, el duplicado deja de ser una rareza teórica para convertirse en una línea del reporte semanal.

Ejemplo trabajado: el mismo event_id, dos veces

Volvamos al evento canónico de Cumbre y sigámoslo cuando el proveedor reintenta.

A las 09:12:03, el proveedor manda el pedido:

{
  "event_id": "evt_8f2a91c4",
  "order_id": "ORD-2041",
  "customer_id": "CUST-118",
  "amount": 2154.00,
  "currency": "MXN"
}

Tu webhook lo recibe, order-triage arranca, y empieza a procesar. Pero tu instancia está ocupada y tarda cuatro segundos en responderle "recibido" al proveedor. El proveedor esperaba respuesta en dos segundos. A las 09:12:05, sin haber recibido tu confirmación, reenvía exactamente el mismo evento:

{
  "event_id": "evt_8f2a91c4",
  "order_id": "ORD-2041",
  "customer_id": "CUST-118",
  "amount": 2154.00,
  "currency": "MXN"
}

Qué esperar. Dos observaciones, y las dos importan.

Primera: los dos eventos son idénticos, campo por campo. Mismo event_id, mismo order_id, mismo amount. No hay ninguna diferencia en los datos que te permita distinguir "este es nuevo" de "este es el reenvío". Y sin embargo hay una pista enorme: el event_id es el mismo. Ese campo —que en la lección 1 dije que parecía redundante— es exactamente lo que el proveedor te dejó para que pudieras decir "ya vi este evento". El proveedor no puede evitar reenviar, pero sí te da la llave para detectar el reenvío. Que uses esa llave es tu trabajo.

Segunda: como viste en la lección 4, n8n crea dos ejecuciones independientes, una por cada disparo. Ninguna sabe de la otra. Sin protección, las dos corren los seis nodos, las dos cobran los $2154. El cliente ve dos cargos.

Aquí está la forma de la solución, aunque no la construyamos todavía: si order-triage tuviera una forma de recordar los event_id que ya procesó, la segunda ejecución podría preguntar "¿ya vi evt_8f2a91c4?", recibir un "sí", y detenerse antes de cobrar. El proveedor sigue reenviando —eso no cambia— pero el segundo reenvío ya no hace daño. Eso es convertir "al menos una vez" en un efecto de "exactamente una vez", desde tu lado.

Y recuerda la trampa de la lección 4: ese "¿ya vi este event_id?" tiene que hacerse con cuidado, porque dos ejecuciones solapadas pueden preguntar a la vez y recibir las dos un "no". Por eso la solución del Módulo 2 no es simplemente "revisar antes de cobrar", sino algo más robusto. Por ahora te basta con ver la pista —el event_id— y la forma de la defensa.

Responder rápido ayuda, pero no resuelve

Hay una técnica que reduce los reenvíos, y conviene conocerla para no confundirla con la solución. No es la solución —la solución es la idempotencia— pero es un buen complemento, y entender por qué no basta afina tu criterio.

Recuerda por qué el proveedor reenvía: porque no recibió tu confirmación a tiempo. De ahí sale una idea razonable: confírmale rápido, antes de hacer todo el trabajo. En lugar de que tu workflow procese el pedido completo —leer el cliente, clasificar, crear, cobrar, enviar— y recién al final le diga "recibido" al proveedor, puedes responderle "recibido" (un 200 OK) apenas el pedido entra, y después procesarlo. n8n permite justo eso: hay una forma de que el webhook responda de inmediato al proveedor y el workflow siga trabajando por su cuenta.

La lógica es buena. Si le confirmas al proveedor en cien milisegundos, es mucho menos probable que se le acabe la paciencia y reenvíe. Con eso eliminas una parte de los reenvíos: los que se producían porque tu procesamiento tardaba más que su tiempo de espera.

Pero fíjate por qué esto reduce los reenvíos sin eliminarlos:

  • Un parpadeo de red puede perder tu 200 OK aunque lo hayas mandado rápido. El proveedor no lo recibe y reenvía igual.
  • El humano que hace doble clic no espera ninguna confirmación tuya: dispara dos veces antes de que tú respondas siquiera.
  • El polling que se solapa no depende de tu tiempo de respuesta.
  • Los reintentos internos de n8n ocurren aguas abajo, después de que ya confirmaste.

Es decir: responder rápido tapa una de las fuentes de duplicado —la del tiempo de espera del proveedor— pero deja abiertas todas las demás. Y hay un matiz más sutil, casi paradójico: si respondes "recibido" antes de procesar, y luego el procesamiento falla, le dijiste al proveedor que todo salió bien cuando no fue así. Responder rápido cambia el contrato de "te confirmo cuando terminé" a "te confirmo que recibí", y eso tiene sus propias consecuencias que el Módulo 5 retoma con el patrón outbox.

La conclusión es la de siempre en esta guía: ninguna técnica que actúe sobre la entrega elimina el duplicado, porque la entrega es "al menos una vez" por diseño. Responder rápido es una buena higiene —reduce el ruido— pero la protección de verdad vive aguas abajo, en hacer que procesar el duplicado no haga daño. Usa las dos: responde rápido para reducir los reenvíos evitables, y haz tus efectos idempotentes para sobrevivir a los inevitables.

El cambio de estrategia que esta lección te pide

Vale la pena hacer explícito el giro mental, porque es el más importante de todo el módulo.

Antes de esta lección, era natural pensar en el duplicado como un problema a prevenir: "tengo que evitar que el mismo pedido entre dos veces". Esa framing te lleva a pelear con los proveedores, a poner candados en el webhook, a tratar cada reintento como un enemigo. Y es una pelea perdida, porque la entrega "al menos una vez" no es un defecto que puedas parchear: es el contrato mismo bajo el que operan los sistemas de los que dependes.

Después de esta lección, el duplicado es un hecho de la vida a absorber: "el mismo pedido va a entrar dos veces, y mi trabajo es que la segunda vez no haga daño". Esa framing te lleva al lugar correcto: a diseñar tus efectos para que repetirlos sea seguro. No peleas con el proveedor; asumes su comportamiento y te proteges aguas abajo.

Podríamos resumirlo así: deja de intentar que el evento llegue una sola vez. Empieza a diseñar para que llegue las veces que sea, sin consecuencias. Ese es el corazón de la mentalidad de dueño aplicada a los duplicados, y es la puerta de entrada al Módulo 2.

Y fíjate en la tranquilidad que trae este giro. Perseguir "exactamente una vez" en la entrega es una carrera contra la física de las redes que nunca ganas del todo: siempre queda un caso, un parpadeo, un tiempo de espera que se te escapa. Diseñar para absorber duplicados es un problema acotado: identificas tus efectos, les pones una defensa, y quedas cubierto sin importar cuántas veces llegue el evento. Cambias una pelea infinita por una tarea finita. Esa es, a fin de cuentas, la razón práctica por la que la industria eligió "al menos una vez más receptor idempotente" en lugar de "exactamente una vez": no porque sea más elegante, sino porque es la única que de verdad se puede terminar.

Errores comunes

Tratar el duplicado como un bug del proveedor (conceptual). Qué pasa: aparece un cobro doble, se rastrea hasta un reenvío del webhook, y la conclusión es "el proveedor está mal, hay que decirle que no reenvíe". Por qué pasa: desde tu lado, el reenvío se ve como un error ajeno. Pero el proveedor reenvía a propósito, porque su garantía es "al menos una vez" y prefiere entregar de más a perder tu evento. Cómo detectarlo: si tu plan para evitar duplicados incluye "pedirle al proveedor que no reintente", vas por el camino equivocado. Cómo corregirlo: acepta el reenvío como parte del contrato y protégete aguas abajo. Un proveedor serio debe reenviar; el que no reenvía es el que pierde eventos, y eso es peor.

Buscar "exactamente una vez" en la entrega (conceptual). Qué pasa: alguien invierte esfuerzo en garantizar que el evento llegue una sola vez —configuraciones, deduplicación en el borde, candados— con la meta de que nunca haya un segundo disparo. Por qué pasa: "exactamente una vez" suena como la solución limpia y definitiva. Cómo detectarlo: si tu diseño intenta evitar que el segundo disparo llegue, en vez de evitar que haga daño, estás persiguiendo lo imposible. Cómo corregirlo: acepta que el segundo disparo va a llegar y muévete a hacerlo inofensivo. El "exactamente una vez" real se construye en la recepción —tu workflow ignora el duplicado— no en la entrega.

Confundir "no me ha pasado" con "no va a pasar" (conceptual). Qué pasa: un workflow lleva meses en producción sin un solo duplicado reportado, y se concluye que el problema no aplica a este caso. Por qué pasa: los duplicados dependen de condiciones —latencia, parpadeos de red, picos de carga— que no ocurren todos los días, así que un workflow puede correr mucho tiempo sin toparse con una. Cómo detectarlo: si tu única evidencia de que estás a salvo es "todavía no ha pasado", no tienes evidencia, tienes suerte. Cómo corregirlo: la ausencia de duplicados en el pasado no dice nada sobre el futuro; dice que las condiciones que los producen aún no coincidieron. Diseña como si fuera a pasar, porque a la escala suficiente, va a pasar.

Creer que un order_id único en tus datos ya te protege (práctico). Qué pasa: alguien ve que cada pedido tiene un order_id distinto y asume que eso, por sí solo, evita los duplicados. Pero cuando el mismo pedido llega dos veces, ¡las dos copias tienen el mismo order_id! Por qué pasa: se confunde "los pedidos distintos tienen ids distintos" con "el sistema usa ese id para detectar repeticiones". Lo primero es cierto; lo segundo requiere que tú construyas la detección. Cómo detectarlo: pregúntate dónde, en tu workflow, algo compara el id entrante contra los ya procesados. Si la respuesta es "en ningún lado", el id no te está protegiendo, solo está presente. Cómo corregirlo: tener un identificador único es necesario pero no suficiente; hace falta un lugar que recuerde los ids ya vistos y una lógica que los descarte. Eso es el Módulo 2 (la clave) y el Módulo 4 (el lugar donde se recuerda).

Ejercicios

Ejercicio 1 — Clasifica las garantías. Para cada situación, di qué garantía de entrega describe: "como mucho una vez", "al menos una vez" o "exactamente una vez".

(a) Un servicio te manda una notificación y, si no confirmas, la reenvía hasta que confirmes. (b) Un sensor manda una lectura de temperatura cada segundo y no le importa si alguna se pierde. (c) El resultado que un cliente experimenta cuando tu workflow ignora los reenvíos y procesa cada pedido una sola vez.

Ver solución

(a) Al menos una vez. Reenvía hasta confirmar, así que nunca pierde, pero puede entregar de más. Es la garantía del repartidor de documentos y la más común en webhooks y pasarelas.

(b) Como mucho una vez. No reintenta; si una lectura se pierde, se perdió. Y está bien para este caso: perder una lectura de temperatura entre mil no importa, y no quieres el costo de garantizar la entrega de cada una. La garantía correcta depende de qué tan grave es perder un evento.

(c) Exactamente una vez, pero fíjate dónde: no en la entrega —el servicio sigue entregando al menos una vez— sino en el efecto observado. El cliente ve el resultado de "exactamente una vez" porque tu workflow, en la recepción, descartó el duplicado. Ese es justo el objetivo de la guía: efecto de exactamente-una-vez sobre una entrega de al-menos-una-vez.

Por qué funciona: la clave es notar que "exactamente una vez" no es una propiedad de la entrega que puedas exigir, sino un resultado que construyes combinando una entrega "al menos una vez" con un receptor que deduplica. Confundir las dos capas es el origen de la mayoría de los diseños fallidos.

Ejercicio 2 — Encuentra la pista de deduplicación. Un proveedor te manda este evento cuando alguien cancela una suscripción. Si el proveedor reenvía por no recibir tu confirmación, ¿qué campo usarías para reconocer que es el mismo evento y no una cancelación nueva? Justifica.

{
  "event_id": "evt_c71b02",
  "type": "subscription.canceled",
  "subscription_id": "sub_4410",
  "customer_id": "CUST-118",
  "canceled_at": "2026-07-20T14:03:00.000Z"
}
Ver solución

El campo correcto es event_id: "evt_c71b02". Es el identificador de la entrega del evento, y un reenvío del mismo evento trae el mismo event_id. Si registras los event_id que ya procesaste, reconoces el reenvío al instante.

Cuidado con la tentación de usar subscription_id o customer_id. El subscription_id identifica la suscripción, no el evento: si esa misma suscripción se cancela, se reactiva y se vuelve a cancelar semanas después, tendrías dos eventos de cancelación legítimos con el mismo subscription_id, y si dedujeras por ese campo, ignorarías la segunda cancelación real. El event_id distingue "el mismo evento reenviado" de "un evento nuevo sobre la misma suscripción", que es justo la distinción que necesitas.

Por qué funciona: la deduplicación necesita una clave que sea única por evento, no por entidad de negocio. El event_id cumple eso; los ids de negocio (suscripción, cliente, pedido) no siempre, porque una misma entidad puede generar varios eventos legítimos. Elegir bien esta clave es el tema central del Módulo 2, y acabas de dar el primer paso.

Ejercicio 3 — Reescribe la estrategia. Un compañero te dice: "Voy a resolver los duplicados configurando el webhook para que rechace cualquier pedido que llegue en menos de cinco segundos después del anterior del mismo cliente". Explica, usando lo de esta lección, por qué esa estrategia es frágil, y cuál es el enfoque correcto.

Ver solución

La estrategia es frágil por varias razones. Primero, pelea con el síntoma en el borde equivocado: intenta evitar que el duplicado entre, cuando el duplicado va a entrar por diseño ("al menos una vez"). Segundo, la ventana de cinco segundos es arbitraria y falla en los dos sentidos: un proveedor puede reenviar a los ocho segundos (y el duplicado pasa el filtro), y un cliente legítimo puede hacer dos pedidos reales distintos en tres segundos (y el segundo, verdadero, se rechaza). Tercero, usa "mismo cliente" como criterio, cuando dos pedidos distintos del mismo cliente son perfectamente normales.

El enfoque correcto no mira el tiempo ni el cliente, sino la identidad del evento. En lugar de "rechaza lo que llegue muy seguido", es "reconoce el event_id que ya procesé y no lo proceses de nuevo". Esa defensa no depende de adivinar una ventana de tiempo ni de suponer que el mismo cliente no repite: se apoya en un identificador que distingue con exactitud el reenvío de un evento nuevo. Y, hecha bien, protege el efecto aunque dos ejecuciones lleguen solapadas.

Por qué funciona: cualquier defensa basada en "tiempo entre eventos" o "mismo cliente" es una heurística que adivina, y las heurísticas fallan en los bordes. Una defensa basada en la identidad del evento no adivina: sabe. Ese es el salto de una solución frágil a una robusta, y es la diferencia entre el instinto del constructor y el criterio del dueño del sistema.

Resumen y siguiente paso

En esta lección entendiste por qué el mismo evento te llega dos veces. Casi todos los disparadores de tus workflows ofrecen entrega "al menos una vez": prefieren entregar de más antes que perder un evento, así que cuando no reciben tu confirmación a tiempo, reenvían. El duplicado no es un accidente ni un bug del proveedor; es el precio, aceptado a propósito, de no perder nunca un evento. Su opuesta ideal, "exactamente una vez", es casi imposible en la entrega, porque cuando una confirmación se pierde, el emisor no puede distinguir "no llegó" de "llegó pero no me enteré", y ante esa duda solo puede arriesgar pérdida o arriesgar duplicado.

La consecuencia reorienta toda la guía: no vas a lograr que los eventos lleguen exactamente una vez, porque eso no depende de ti. Vas a lograr que tu workflow, en la recepción, reconozca el duplicado y no lo procese dos veces —construyendo un efecto de "exactamente una vez" sobre una entrega de "al menos una vez"—. Viste los cuatro caminos por los que un pedido llega dos veces a order-triage —proveedor que reintenta, polling que se solapa, n8n que reintenta, humano que hace doble clic—, que todos comparten la misma causa profunda, y que por eso la defensa correcta es una sola, aguas abajo, apoyada en el event_id que el proveedor te deja justamente para eso.

Antes de avanzar deberías poder: explicar en una frase qué significa "al menos una vez" y por qué es la garantía más común; explicar por qué "exactamente una vez" es casi imposible en la entrega y dónde sí se puede construir; y nombrar el campo del evento de Cumbre que usarías para reconocer un reenvío.

Ya tienes el origen del duplicado (esta lección) y su mecánica (lección 4). Lo que falta es ver el duplicado junto a sus hermanos, porque el doble disparo es solo uno de cuatro modos de falla que un dueño del sistema debe mapear. La lección 6 te da los cuatro —fallo parcial, doble disparo, orden alterado y cambio de esquema aguas arriba— y un método para listarlos sobre un flujo concreto, que es el paso directo hacia la auditoría con la que cierra el módulo.

Recursos

  • Webhook node — n8n Docs — la ficha del nodo que recibe los eventos; revisa cómo responde al proveedor, porque una respuesta rápida reduce (aunque no elimina) los reenvíos por tiempo de espera.
  • Respond to Webhook node — n8n Docs — el nodo con el que controlas cuándo y cómo le confirmas al proveedor; útil para entender la carrera entre tu confirmación y su reenvío.
  • Handling API rate limits — n8n Docs — sobre reintentos y esperas en las llamadas salientes; muestra el otro lado del mismo fenómeno, cuando eres tú quien reintenta contra un servicio.
  • Idempotency and at-least-once delivery — referencia general — una entrada de referencia sobre idempotencia; útil para ver que "al menos una vez más receptor idempotente" es un patrón estándar de la ingeniería distribuida, no una idea exclusiva de n8n.