¿Cómo puedo evitar ejecuciones duplicadas de flujo de trabajo cuando un webhook se activa varias veces?

¿Cómo puedo evitar ejecuciones de flujo de trabajo duplicadas cuando se dispara un webhook varias veces?

¿Cuál es el mensaje de error (si lo hay)?

Webhook Set HTTP Request Airtable Soy consciente de que Stripe reintenta las solicitudes fallidas, pero incluso las exitosas a veces parecen llegar dos veces. ¿Cómo puedo asegurarme de que cada evento se procese solo una vez?

Comparte tu flujo de trabajo

(Selecciona los nodos en tu lienzo y usa los atajos de teclado CMD+C/CTRL+C y CMD+V/CTRL+V para copiar y pegar el flujo de trabajo.)

Comparte el resultado devuelto por el último nodo

Estoy usando un nodo Webhook que recibe eventos de Stripe. Ocasionalmente, el mismo evento se entrega más de una vez, lo que causa que mi flujo de trabajo procese pedidos duplicados

Información sobre tu configuración de n8n

  • Versión de n8n: 1.123
  • Base de datos (predeterminada: SQLite): PostgreSQL
  • Configuración n8n EXECUTIONS_PROCESS (predeterminada: own, main):
  • Ejecutar n8n mediante (Docker, npm, n8n cloud, aplicación de escritorio):
  • Sistema operativo:

Hola @Fatoki_Alfred

Cada evento de Stripe contiene un id único (por ejemplo, evt_1Nabc...). Para asegurar que cada evento se procesa solo una vez, debes rastrear estos IDs en una tabla dedicada de «eventos procesados» en tu base de datos Postgres.

Hola @Fatoki_Alfred
n8n tiene un nodo Remove Duplicates integrado que hace esto sin necesidad de tabla personalizada ni consulta. Colócalo justo después del nodo Webhook y configura Operation en „Remove Items Processed in Previous Executions

Complementando las respuestas anteriores: el punto que suele pasar desapercibido es la condición de carrera (race condition). Si Stripe entrega el mismo evt_id en paralelo, dos workflows pueden pasar por la verificación de deduplicación ANTES de que cualquiera de ellos registre el id — y ambos continúan. El nodo Remove Duplicates resuelve el caso serial, pero no es atómico bajo concurrencia.

Para protegerse de verdad, haz la deduplicación en la propia base de datos: crea la columna con restricción UNIQUE (ej.: stripe_event_id TEXT UNIQUE) y, justo después del Webhook, un nodo Postgres con INSERT … ON CONFLICT (stripe_event_id) DO NOTHING RETURNING id. Si el RETURNING viene vacío, es reentrega duplicada → detén el flujo ahí (un IF verificando si hay fila retornada). La base de datos garantiza atomicidad, así que incluso dos entregas simultáneas: solo una gana el INSERT.

Dos cosas extras que reducen mucho el problema en su origen: (1) responde 200 a Stripe lo más rápido posible (usa el Webhook en modo Respond Immediately o un nodo Respond to Webhook al inicio) — los timeouts hacen que Stripe reintente y es ahí donde nacen muchas ‘duplicatas’; y (2) procesa el pedido solo después de que el INSERT de control pase. Así el id del evento es tu clave de idempotencia real, y la lógica del pedido nunca se ejecuta dos veces.

Hola @Fatoki_Alfred

Stripe reintentar deliberadamente las entregas de webhooks, por lo que los eventos duplicados son esperados. El enfoque recomendado es hacer que tu flujo de trabajo sea idempotente en lugar de asumir que cada webhook es único.
La solución más simple es almacenar el event.id antes de procesarlo. Al inicio del flujo de trabajo:

  • Extrae event.id.
  • Consulta tu base de datos (o Data Store) para ese ID.
    Si ya existe, detén el flujo de trabajo.
    De lo contrario, guarda el ID y continúa procesando.
    Usualmente, usar un nodo IF antes de tu lógica empresarial es suficiente.
    Si estás escribiendo en Airtable u otra base de datos, considera hacer que event.id sea un campo único para que los duplicados se rechacen automáticamente.

Trata los webhooks de Stripe como entrega al menos una vez e implementa el flujo de trabajo de manera idempotente. Usa el ID de evento de Stripe como clave de idempotencia. Al inicio del flujo de trabajo, verifica la firma del webhook e intenta insertar ese ID de evento en una tabla de PostgreSQL con una restricción única. Continúa solo si la inserción tiene éxito. Si el ID ya existe, devuelve una respuesta exitosa y detente sin crear el pedido nuevamente.

Una tabla simple puede contener event_id como clave principal más status, received_at y completed_at. En el nodo de Postgres usa una inserción con ON CONFLICT DO NOTHING y devuelve si se insertó una fila. Envía ese resultado a un nodo IF. La rama true procesa el pedido y la rama false sale. La restricción de base de datos importa porque dos copias pueden llegar lo suficientemente cerca como para que una verificación de búsqueda-luego-inserción separada permita que ambas pasen.

Marca el registro como en procesamiento cuando se reclama y completado solo después de que el pedido tenga éxito. Decide cómo se deben reintentar los registros fallidos para que un error temporal no suprima permanentemente el evento. También pasa el ID de evento de Stripe a los sistemas descendentes como su clave de idempotencia donde se admita. No deduplicar por cliente, cantidad o marca de tiempo porque pagos legítimos separados pueden compartir esos valores.

Crea una clave de idempotencia a partir de un ID de evento estable y almacénala antes de que se ejecuten los nodos costosos. Si llega la misma clave nuevamente, devuelve el resultado anticipadamente y establece una expiración que coincida con cuánto tiempo el remitente puede reintentar.