Módulo 2: Idempotencia: que repetir no duplique
3. Claves de idempotencia: naturales vs sintéticas
Descripción
Al terminar esta lección vas a poder elegir la clave de idempotencia correcta para un evento —la etiqueta que dice "este pedido y aquel son el mismo, no dos distintos"— y vas a saber cuándo usar una clave que ya viene en el dato (natural) y cuándo fabricar una tú (sintética). Vas a calcular una clave sintética estable con un hash dentro de un nodo Code, usando el módulo crypto, y vas a reconocer de inmediato el error que arruina todo el mecanismo: elegir una clave que cambia en cada ejecución.
Esto importa porque la clave es la materia prima de toda la idempotencia. El upsert de la lección 4 necesita una clave para saber por cuál columna no duplicar. La cabecera de la lección 5 necesita una clave para mandársela a la pasarela. La deduplicación del módulo 4 necesita una clave para recordar qué ya procesó. Si la clave está mal elegida, ninguna de esas herramientas funciona —y lo peor es que parecen funcionar en la prueba y fallan en producción, que es la forma más cara de fallar—. Elegir bien la clave es, sin exagerar, la mitad de este módulo.
Conexión con el módulo: la lección 2 te enseñó a reconocer un efecto que no es idempotente. Esta te da lo primero que necesita cualquier reparación: un nombre estable para el evento. Todo lo que sigue lo consume. La lección 4 usa la clave como la columna del upsert; la lección 5 la manda en la cabecera Idempotency-Key; la lección 6 muestra por qué la clave tiene que vivir dentro de la operación atómica y no en un nodo aparte; la lección 7 la deriva de la decisión de un agente; y el proyecto de la lección 8 arranca, precisamente, eligiendo la clave de ORD-2041.
Qué es una clave de idempotencia
Empecemos por definirla, porque el nombre suena más técnico de lo que es.
Una clave de idempotencia es un valor que identifica de forma única un evento, de modo que dos llegadas del mismo evento comparten la misma clave y dos eventos distintos tienen claves distintas.
Piénsalo con el ticket del guardarropa de un teatro. Llegas con tu abrigo, te lo reciben y te dan un ticket con el número 47. Ese número identifica tu abrigo. Cuando vuelvas, entregas el 47 y te devuelven exactamente tu abrigo, no el de otra persona. Si por alguna razón entregas el 47 dos veces —digamos que hiciste una fila equivocada y volviste a formarte— el guardarropa mira el 47, ve que ese abrigo ya te lo entregó, y no te da un segundo abrigo que no existe. El número del ticket es la clave: mismo abrigo, mismo número; abrigos distintos, números distintos.
En Cumbre, la clave de idempotencia del pedido ORD-2041 es lo que le va a permitir a la pasarela de pago decir "este cobro ya lo hice" cuando el webhook llegue por segunda vez. Es el número 47 del guardarropa. Toda la maquinaria de idempotencia se cuelga de ese número.
Para que una clave sirva, tiene que cumplir tres propiedades. Vale la pena nombrarlas porque cada error de esta lección es violar una de las tres:
Determinista. La misma entrada siempre produce la misma clave. Si calculas la clave a partir del pedido, calcularla mil veces sobre el mismo pedido da mil veces el mismo resultado. Sin esto, nada funciona.
Estable entre reintentos. La clave del mismo evento es idéntica en la primera llegada, en la segunda y en la quinta. Esta es la propiedad que más se rompe, y casi siempre por la misma razón: incluir en la clave algo que cambia entre llegadas, como la hora en que llegó.
Específica. Eventos distintos producen claves distintas. Si dos pedidos genuinamente diferentes terminan con la misma clave, el sistema va a creer que el segundo es un duplicado del primero y lo va a descartar. Eso es un pedido perdido, que es tan malo como un pedido duplicado.
Una clave que cumple las tres es un buen número de guardarropa. Una que falla en cualquiera de las tres es una fuente de bugs.
La clave natural: la que ya venía en el dato
La forma más simple y más robusta de conseguir una clave es no fabricarla: usar una que ya viene en el evento. Eso es una clave natural.
En el pedido de Cumbre, el candidato es obvio:
{
"order_id": "ORD-2041",
"customer_id": "CUST-118",
"amount": 1780,
...
}
El order_id —ORD-2041— es un identificador que la tienda en línea asignó al pedido en el momento de crearlo. Y aquí está la propiedad que lo hace oro: cuando la tienda reintenta el webhook, manda el mismo order_id. No genera uno nuevo; reenvía el pedido tal cual, con su ORD-2041 intacto. Por eso el order_id cumple las tres propiedades sin que tú hagas nada: es determinista (es un valor fijo), estable entre reintentos (la tienda manda el mismo), y específico (cada pedido real tiene el suyo).
Cuando existe una clave natural buena, úsala. No fabriques una sintética por gusto. La clave natural es más simple, más legible y más fácil de depurar: cuando veas un duplicado en producción, poder buscar por ORD-2041 en tus registros vale muchísimo más que buscar por un hash de sesenta caracteres.
¿Qué hace "buena" a una clave natural? Que el sistema de origen la asigne una vez y la conserve en los reintentos. El order_id de una tienda seria lo hace. El número de factura, el id de transacción de un banco, el message-id de un correo, el id de evento que muchas plataformas incluyen justo para esto —muchas APIs de webhooks mandan un event_id o un delivery_id que es el mismo en cada reintento, y existe precisamente para que lo uses como clave de idempotencia—: todos son claves naturales excelentes.
La pregunta que decide si una clave natural sirve es una sola: ¿este identificador es el mismo cuando el evento llega dos veces? Si la respuesta es sí, ya tienes tu clave. Si es no, necesitas una sintética.
La clave sintética: la que fabricas tú
A veces no hay una clave natural buena. El evento llega sin un identificador estable, o el identificador que trae cambia entre llegadas. Ahí fabricas una clave sintética: la calculas a partir del contenido del evento.
Volvamos a Cumbre, pero por otro canal. Los pedidos que entran por WhatsApp no siempre traen un order_id —el cliente llenó un formulario simple y el sistema no le asignó folio—. Un pedido así puede verse así:
{
"channel": "whatsapp",
"customer_id": "CUST-204",
"amount": 940,
"line_items": [
{ "sku": "TE-CHM-100", "quantity": 4 }
]
}
No hay order_id. Si el webhook de WhatsApp llega dos veces, ¿cómo sabes que son el mismo pedido y no dos pedidos que casualmente se parecen? Necesitas una clave, y la única materia prima que tienes es el contenido. La fabricas con un hash.
Un hash es una función que toma un texto de cualquier tamaño y devuelve una cadena de longitud fija que actúa como su "huella digital". La propiedad que nos sirve: el mismo texto de entrada siempre produce la misma huella, y textos distintos producen huellas distintas. Si construimos un texto con los campos que definen el pedido —el cliente, el monto, los productos— y lo pasamos por un hash, obtenemos una clave que es idéntica si el contenido es idéntico. Determinista y estable: justo lo que pedíamos.
Ejemplo trabajado: calcular una clave sintética con crypto
Vamos a calcularla en un nodo Code. Recuerda la restricción que ya conoces: dentro del nodo Code de n8n 2.0 no puedes hacer peticiones HTTP ni tocar el sistema de archivos, y en n8n Cloud solo tienes disponibles dos módulos: crypto y moment. Resulta que crypto es exactamente lo que necesitamos para hashear —calcular una huella es un cálculo local, no una llamada a ningún lado—, así que esto sí se puede hacer aquí.
Pon un nodo Code después del Webhook, en modo Run Once for Each Item, con este código:
// Nodo: Code — "Compute idempotency_key"
// Modo: Run Once for Each Item
// Objetivo: darle a cada pedido una clave estable derivada de su contenido.
const crypto = require('crypto'); // el módulo de hashing; en el Code node de n8n está permitido
const order = $input.item.json;
// Construyo un texto con SOLO los campos que definen "el mismo pedido".
// Ojo: nada de fechas de llegada ni ids aleatorios. Solo contenido estable.
const fingerprint = [
order.customer_id,
order.amount,
order.line_items.map((line) => `${line.sku}x${line.quantity}`).join(','),
].join('|');
// sha256 devuelve la huella; 'hex' la formatea como texto legible de 64 caracteres.
const idempotencyKey = crypto
.createHash('sha256')
.update(fingerprint)
.digest('hex');
return {
json: {
...order, // conservo todo lo que ya venía
idempotency_key: idempotencyKey, // y agrego la clave calculada
},
};
Desmenucemos las piezas, porque cada línea tiene un porqué.
require('crypto') trae el módulo de hashing. Es una de las dos excepciones que el nodo Code permite; crypto no sale a internet, solo hace cuentas con lo que le das.
fingerprint es el texto que resume el pedido. Y aquí está la decisión más importante de todo el código: qué campos incluyo. Puse customer_id, amount y una representación de las líneas (TE-CHM-100x4). No puse la hora de llegada, ni el canal, ni ningún valor que pueda diferir entre la primera y la segunda llegada del mismo pedido. La regla es: incluye lo que hace idéntico al pedido, excluye lo que puede variar entre reintentos.
createHash('sha256').update(fingerprint).digest('hex') es el cálculo. sha256 es un algoritmo de hash estándar y confiable; update le pasa el texto; digest('hex') pide el resultado en formato hexadecimal, una cadena de 64 caracteres como a3f1c.... El mismo fingerprint produce siempre el mismo hash.
Qué esperar al ejecutarlo. El panel OUTPUT muestra el pedido con un campo nuevo, idempotency_key, con un valor tipo "a3f1c9e2...". (64 caracteres). Si vuelves a ejecutar el nodo con el mismo pedido de entrada, la clave es exactamente la misma. Si cambias cualquiera de los campos que entran al fingerprint —el monto, la cantidad de una línea— la clave cambia por completo. Esa es la prueba de que la clave es determinista y específica: mismo contenido, misma clave; contenido distinto, clave distinta.
Ese idempotency_key es lo que vas a pasarle al upsert (lección 4) y a la cabecera de la API (lección 5). Acabas de fabricar el número del guardarropa.
La decisión fina: qué campos entran al hash
Aquí hay una trampa que merece atención, porque es donde se equivocan hasta los experimentados.
El hash define qué significa "el mismo pedido". Si incluyes de más, rompes la estabilidad. Si incluyes de menos, rompes la especificidad. Es un equilibrio.
Imagina que hubieras incluido created_at (la hora en que llegó) en el fingerprint. La primera llegada trae 09:12:00; el reintento, tres segundos después, trae 09:12:03. Textos distintos, hashes distintos, claves distintas para el mismo pedido. La pasarela vería dos eventos nuevos y volverías a los dos cobros. Incluir la hora de llegada es el error clásico, y la próxima sección lo trata a fondo.
Ahora el problema opuesto. Imagina que Cumbre tiene un cliente que, legítimamente, hace el mismo pedido dos veces el mismo día —CUST-204 pide cuatro tés de manzanilla en la mañana y otros cuatro en la tarde, dos pedidos reales y distintos—. Si tu fingerprint solo tiene customer_id, amount y líneas, los dos pedidos producen la misma clave, y tu sistema descartaría el segundo creyéndolo un duplicado. Le robaste una venta a Cumbre. Aquí incluiste de menos: te falta algo que distinga dos pedidos reales del mismo contenido.
¿Cómo se resuelve? Depende de qué información tengas. Si el formulario de WhatsApp incluye un identificador de sesión o de mensaje que sea único por envío real pero estable en el reintento, ese es tu mejor amigo: inclúyelo. Si de plano no hay nada que distinga dos pedidos idénticos, tienes que negociar con la realidad del negocio —quizás una ventana de tiempo gruesa ("mismo cliente, mismo contenido, dentro de la misma hora = mismo pedido") sea aceptable, asumiendo el pequeño riesgo de fusionar dos pedidos genuinos muy seguidos—. No hay una respuesta universal; hay una decisión de diseño que tú tomas con conocimiento del negocio. Lo que esta lección te da es la conciencia de que la decisión existe.
La conclusión honesta: una clave natural buena te ahorra toda esta agonía. Por eso, cuando el evento trae un order_id estable, se prefiere. La clave sintética es para cuando no hay más remedio, y entonces la calidad de tu clave es tan buena como tu elección de campos.
Dónde vive la clave en el workflow
Un detalle práctico que conviene fijar antes de seguir: ¿en qué punto de order-triage se calcula la clave, y cómo llega a los nodos que la usan?
La clave se calcula una sola vez, lo antes posible, y después viaja con el item hacia los nodos que la necesitan. En order-triage queda así:
Webhook ──► Code ──► AI Agent ──► HTTP Request
(calcula (clasifica) (usa idempotency_key
idempotency_key en el upsert y
y lo agrega al item) en la cabecera)
Fíjate en tres decisiones de este diseño.
Se calcula temprano, justo después del Webhook. Así, todo lo que venga después —el agente, el HTTP Request— ya tiene la clave disponible en el item. Calcularla al final, junto al efecto, funciona, pero calcularla temprano deja el dato listo para cualquier nodo que lo necesite, incluidos los que agregarás más adelante.
Se calcula una vez, no en cada nodo. Si dos nodos distintos calcularan la clave por su cuenta, correrías el riesgo de que uno incluya un campo que el otro no, y terminarías con dos claves distintas para el mismo pedido —el desastre silencioso—. Una sola fuente de verdad: un nodo la calcula, los demás la leen.
Viaja como un campo más del item. Al hacer { ...order, idempotency_key: idempotencyKey }, la clave se vuelve parte del pedido que fluye por el workflow. Cualquier nodo posterior la lee con una expresión normal, por ejemplo {{ $json.idempotency_key }} en un campo del HTTP Request. No hay magia; es un campo como order_id o amount.
Hay una sutileza que la lección 6 va a desarrollar, así que la dejo sembrada: calcular la clave en un nodo y usarla en otro nodo distinto está bien para derivar la clave, pero no convierte por sí solo la operación en idempotente. La idempotencia real ocurre cuando esa clave se aplica dentro de una operación atómica —el upsert de la base de datos, la cabecera que la API procesa atómicamente—. Calcular la clave es el paso 1; aplicarla en el lugar correcto es lo que de verdad protege. Por ahora quédate con que la clave se calcula temprano y viaja con el item.
El error que arruina todo: la clave que cambia cada vez
Si te llevas una sola advertencia de esta lección, que sea esta, porque es el error más común y el más caro.
Nunca uses como clave de idempotencia un valor que cambia en cada ejecución. Los dos culpables de siempre:
La marca de tiempo. new Date(), Date.now(), la hora actual, created_at si se genera al procesar en vez de al crear el pedido. Cada ejecución ocurre en un instante distinto, así que la clave es distinta cada vez. El reintento tres segundos después tiene una clave nueva, el sistema lo ve como nuevo, y duplicas. Una marca de tiempo es lo opuesto de una clave estable: es un valor diseñado, literalmente, para ser distinto cada vez.
El identificador aleatorio nuevo por ejecución. crypto.randomUUID(), un número al azar, un id que generas tú en el momento. Un UUID recién generado es único por definición —para eso existe— así que dos ejecuciones del mismo evento producen dos UUID distintos y dos claves distintas. Es el mismo error con otro disfraz.
El síntoma es cruel: funciona en la prueba y falla en producción. Cuando pruebas, disparas el evento una vez, ves un cobro, todo bien. La clave "cambiante" nunca se pone a prueba porque nunca hubo un segundo disparo con "la misma" clave —de hecho no existe "la misma" clave, cada disparo tiene la suya—. El día que el proveedor reintente en producción, la clave nueva no coincide con nada, y aparece el duplicado que tu prueba juró que no existía.
La regla mental para no caer: pregúntate "si este mismo evento llegara otra vez dentro de cinco segundos, ¿mi clave sería idéntica?". Si tu clave depende de la hora o de un aleatorio, la respuesta es no, y tienes un bug esperando. Si depende solo del contenido estable del evento, la respuesta es sí, y estás a salvo. Esta pregunta es tan barata de hacer y tan cara de omitir que conviene convertirla en un reflejo: cada vez que definas una clave, hazla en voz alta antes de seguir.
Fíjate en la ironía útil: crypto.randomUUID() sirve para generar identificadores únicos, y crypto.createHash() sirve para generar identificadores estables. El mismo módulo, dos funciones, propósitos opuestos. Para una clave de idempotencia quieres estabilidad, así que quieres createHash sobre el contenido, nunca randomUUID.
Errores comunes
Meter la marca de tiempo en la clave (práctico). Qué pasa: alguien construye la clave incluyendo created_at, Date.now() o la hora de procesamiento, la prueba con un solo disparo, funciona, y la manda a producción. Semanas después aparecen cobros duplicados que "no deberían existir". Por qué pasa: la marca de tiempo es distinta en cada llegada, así que el reintento genera una clave nueva que el sistema no reconoce como repetida. Y como la prueba manual casi nunca dispara el mismo evento dos veces con segundos de diferencia, el bug no se manifiesta hasta que el proveedor reintenta en producción. Cómo detectarlo: lee tu fingerprint o tu clave y busca cualquier cosa que dependa del reloj; si está, tienes el bug. También ayuda la pregunta "¿sería idéntica si el evento llegara de nuevo en cinco segundos?". Cómo corregirlo: saca del cálculo todo lo temporal. La clave se deriva solo del contenido que identifica al evento —el order_id natural, o un hash de los campos estables—, nunca de cuándo llegó.
Generar un UUID nuevo por ejecución (práctico). Qué pasa: en el nodo Code, alguien pone const key = crypto.randomUUID() pensando "necesito un identificador único para el cobro". Cada ejecución produce un UUID nuevo, así que la idempotencia nunca se activa. Por qué pasa: se confunde "único" con "estable". Un cobro sí necesita un identificador único en el mundo, pero la clave de idempotencia necesita ser la misma entre reintentos del mismo evento. Son requisitos opuestos y randomUUID cumple el primero, no el segundo. Cómo detectarlo: si en tu código aparece randomUUID(), Math.random() o cualquier fuente de azar dentro del cálculo de la clave, está mal. Cómo corregirlo: reemplaza el aleatorio por un hash determinista del contenido (createHash('sha256')) o, mejor aún, por la clave natural del pedido si existe. Guarda randomUUID para cuando de verdad necesites un identificador nuevo —que no es este caso—.
Usar una clave natural que el origen no conserva (práctico). Qué pasa: alguien elige como clave natural un campo que parece estable pero que el sistema de origen regenera en cada envío —por ejemplo, un request_id que la plataforma emisora crea nuevo por cada intento de entrega, no por cada evento—. Suena a clave natural, pero cambia entre reintentos. Por qué pasa: no todo identificador que viene en el payload es estable; algunos identifican la entrega, no el evento, y esos cambian justo cuando reintenta. Cómo detectarlo: no asumas; verifica en la documentación del proveedor si el campo es "el mismo en los reintentos". Muchas plataformas lo dicen explícitamente y hasta separan un event_id (estable) de un delivery_id (cambia por intento). Cómo corregirlo: usa el identificador que el proveedor garantiza estable entre reintentos —a menudo llamado event_id o similar—; si no hay ninguno garantizado, cae a una clave sintética por contenido.
Ejercicios
Ejercicio 1 — ¿Natural o sintética? Para cada evento, decide si usarías una clave natural (y cuál campo) o una sintética (y qué campos hashearías), y justifica en una frase:
(a) Un pedido web de Cumbre que trae order_id: "ORD-2041", y sabes que la tienda reenvía el mismo order_id en los reintentos.
(b) Un formulario de WhatsApp sin order_id, con customer_id, amount y line_items.
(c) Un webhook de una pasarela de pago que trae un campo event_id que, según su documentación, es idéntico en todos los reintentos de la misma notificación.
(d) Un evento que trae request_id, y la documentación dice que ese id es distinto en cada intento de entrega.
Ver solución
(a) Natural: order_id. Es un identificador estable que el origen conserva en los reintentos. Cumple las tres propiedades sin esfuerzo. Úsalo tal cual; no fabriques un hash pudiendo usar ORD-2041.
(b) Sintética: hash de customer_id + amount + representación de line_items. No hay id estable, así que derivas la clave del contenido. Cuidado con el caso de dos pedidos legítimos idénticos del mismo cliente el mismo día: si el formulario trae algún id de sesión o mensaje estable, inclúyelo para distinguirlos.
(c) Natural: event_id. La documentación garantiza que es el mismo en los reintentos. Es una clave natural de manual —de hecho, muchas plataformas incluyen ese campo precisamente para que lo uses así—. No inventes una sintética.
(d) Sintética, NO uses request_id. Aunque venga en el payload y parezca un id, la documentación dice que cambia en cada intento: identifica la entrega, no el evento. Usarlo daría una clave distinta por reintento y duplicarías. Deriva una clave sintética del contenido estable del evento.
Por qué funciona: las cuatro se deciden con la misma pregunta —"¿este identificador es idéntico cuando el evento llega dos veces?"—. Si sí y ya viene en el dato, natural. Si no viene, o viene pero cambia, sintética por contenido. El caso (d) es la trampa: un id en el payload no es automáticamente una buena clave.
Ejercicio 2 — Encuentra el veneno en el hash. Un compañero escribió este cálculo de clave para los pedidos de WhatsApp. Tiene un problema que hará que la idempotencia nunca funcione. Encuéntralo y corrígelo:
const crypto = require('crypto');
const order = $input.item.json;
const fingerprint = [
order.customer_id,
order.amount,
new Date().toISOString(), // "para que la clave sea única"
].join('|');
const idempotencyKey = crypto.createHash('sha256').update(fingerprint).digest('hex');
Ver solución
El veneno es new Date().toISOString(). Ese valor es la hora actual, en el momento en que corre el nodo, y es distinto cada vez que el workflow se ejecuta. La primera llegada del pedido produce, digamos, 2026-07-14T09:12:00Z; el reintento tres segundos después produce 2026-07-14T09:12:03Z. Como el fingerprint incluye esa hora, los dos hashes son completamente distintos, y la pasarela ve dos eventos nuevos. La idempotencia nunca se activa: cada llegada tiene su propia clave.
El comentario "para que la clave sea única" delata la confusión de fondo —confundir único con estable—. La clave de idempotencia no debe ser única por ejecución; debe ser la misma para el mismo evento entre ejecuciones.
La corrección es quitar la línea de la fecha por completo:
const fingerprint = [
order.customer_id,
order.amount,
order.line_items.map((line) => `${line.sku}x${line.quantity}`).join(','),
].join('|');
Ahora el fingerprint depende solo del contenido estable del pedido. La misma llegada, dos veces, produce la misma clave. (Agregué las líneas al fingerprint en lugar de la fecha, para ganar especificidad sin sacrificar estabilidad.)
Por qué funciona: aplicaste la pregunta clave —"¿sería idéntica si el evento llegara de nuevo en cinco segundos?"—. Con la fecha adentro, no. Sin ella, sí. Ese es todo el criterio.
Ejercicio 3 — Diseña la clave para los tres canales. Cumbre recibe pedidos por tres canales con datos de distinta calidad. Para cada uno, propón una estrategia de clave y explica el riesgo principal que debes cuidar:
web: traeorder_idestable.whatsapp: sinorder_id, traecustomer_id,amount,line_itemsy unsession_idque la app garantiza estable por envío.rep_csv: un archivo que suben los vendedores, con filas que a veces se repiten dentro del mismo archivo por error humano, sin ningún id estable.
Ver solución
web: clave natural order_id. Riesgo a cuidar: confirmar que la tienda de verdad reenvía el mismo order_id en los reintentos y no genera uno nuevo. Si lo confirmas (idealmente en su documentación), no hay más que hacer.
whatsapp: clave sintética que incluya el session_id (estable por envío) junto con el contenido: hash(session_id + customer_id + amount + líneas). El session_id te da especificidad —distingue dos pedidos legítimos idénticos del mismo cliente— sin sacrificar estabilidad, porque es el mismo en el reintento. Riesgo a cuidar: no meter la hora de llegada; el session_id ya aporta la unicidad que necesitas.
rep_csv: clave sintética por contenido de cada fila: hash(customer_id + amount + líneas). Aquí el riesgo es doble y opuesto. Por un lado, las filas duplicadas dentro del mismo archivo deben colapsar en la misma clave —y eso es bueno, es justo lo que quieres, que el pedido repetido por error no se procese dos veces—. Por otro, si dos vendedores capturan pedidos reales idénticos de clientes distintos, el customer_id los distingue; pero dos pedidos reales idénticos del mismo cliente colisionarían. Sin un id estable, esa colisión es un riesgo asumido que hay que discutir con el negocio: ¿es aceptable, o hace falta pedirle a los vendedores un folio manual? La ingeniería honesta nombra ese riesgo en vez de esconderlo.
Por qué funciona: los tres canales muestran el abanico completo. Cuando hay un id estable natural, se usa (web). Cuando no, pero hay un id de sesión estable, se combina con el contenido (whatsapp). Cuando no hay nada estable, se hashea el contenido asumiendo y declarando el riesgo de colisión (rep_csv). La calidad de tus datos de origen determina la calidad de tu clave, y parte del trabajo es ser honesto sobre esa calidad.
Resumen y siguiente paso
En esta lección aprendiste que toda la idempotencia se cuelga de una clave: un valor que hace que dos llegadas del mismo evento compartan identidad y dos eventos distintos no la compartan. La imaginaste como el número del guardarropa —mismo abrigo, mismo número— y le pusiste tres propiedades: determinista, estable entre reintentos, y específica. Viste las dos formas de conseguirla: la natural, un identificador que ya viene en el dato y que el origen conserva en los reintentos (el order_id de Cumbre, un event_id de un webhook), que es siempre la primera opción cuando existe; y la sintética, un hash del contenido que fabricas con crypto.createHash('sha256') en un nodo Code cuando no hay clave natural buena —cuidando la decisión fina de qué campos entran al hash, para no romper ni la estabilidad ni la especificidad—. Y grabaste el error más caro del módulo: usar una marca de tiempo o un UUID nuevo por ejecución, que produce una clave distinta cada vez, funciona en la prueba y duplica en producción.
Antes de avanzar a la lección 4 deberías poder: elegir entre clave natural y sintética según si el evento trae un id estable; calcular una clave sintética con un hash sin incluir nada temporal; y explicar por qué randomUUID() es exactamente lo que no quieres para una clave de idempotencia.
Ya tienes el número del guardarropa. La lección 4 lo usa para lo primero: no duplicar en tu propia base de datos. Vas a conocer el upsert —"inserta si no existe, actualiza si existe, pero nunca dupliques"— que convierte una tabla en un conjunto por clave, y vas a ver cómo lo expresan el nodo de base de datos y el de hoja de cálculo en n8n. Es la herramienta que arregla el INSERT a ciegas del CRM de Cumbre, y la que más vas a usar en tu vida real con workflows.
Recursos
- Crypto — Node.js documentation — la referencia del módulo
cryptoque usas en el nodoCode:createHash,update,digesty sus opciones. Es Node.js estándar, disponible dentro del Code node de n8n. - Code node — allowed modules — n8n Docs — qué módulos externos e integrados están disponibles dentro del nodo
Code; confirma quecryptoymomentson los que tienes en n8n Cloud. - Idempotency — Stripe API reference — cómo una API real describe qué debe ser una buena clave de idempotencia (estable, única por operación) y cuánto tiempo la recuerda. Verifica siempre las condiciones vigentes.
- Webhooks best practices — n8n Docs — el nodo que recibe el evento del que sacas la clave natural; útil para entender qué campos llegan y cuáles conviene inspeccionar antes de elegir la clave.