Como posso evitar execuções de workflow duplicadas quando um webhook é acionado várias vezes?
Qual é a mensagem de erro (se houver)?
Webhook Set HTTP Request Airtable Estou ciente de que o Stripe tenta novamente solicitações com falha, mas até as bem-sucedidas às vezes parecem chegar duas vezes. Como posso garantir que cada evento seja processado apenas uma vez?
Por favor, compartilhe seu workflow
(Selecione os nós na sua tela e use os atalhos de teclado CMD+C/CTRL+C e CMD+V/CTRL+V para copiar e colar o workflow.)
Compartilhe a saída retornada pelo último nó
Estou usando um nó Webhook que recebe eventos do Stripe. Ocasionalmente, o mesmo evento é entregue mais de uma vez, causando que meu workflow processe pedidos duplicados
Informações sobre sua configuração n8n
- Versão n8n: 1.123
- Banco de dados (padrão: SQLite): PostgreSQL
- Configuração n8n EXECUTIONS_PROCESS (padrão: own, main):
- Executando n8n via (Docker, npm, n8n cloud, aplicativo desktop):
- Sistema operacional:
Oi @Fatoki_Alfred
Cada evento do Stripe contém um id único (por exemplo, evt_1Nabc...). Para garantir que cada evento seja processado apenas uma vez, você deve rastrear esses IDs em uma tabela dedicada de “eventos processados” no seu banco de dados Postgres.
Oi @Fatoki_Alfred
n8n tem um nó Remove Duplicates integrado que faz isso sem nenhuma tabela ou consulta customizada. Coloque-o logo após o nó Webhook e defina Operation como “Remove Items Processed in Previous Executions”, Keep Items Where como “Value Is New” e Value to Dedupe On como o id do evento Stripe:
{{ $json.body.id }}
n8n mantém o histórico de ids vistos em seu próprio banco de dados Postgres, então persiste entre reinicializações, e qualquer entrega repetida é descartada antes de chegar aos seus passos de pedido.
Complementando as respostas acima: o ponto que costuma escapar é a corrida (race condition). Se o Stripe entrega o mesmo evt_id em paralelo, dois workflows podem passar pela checagem de dedupe ANTES de qualquer um gravar o id — e os dois seguem. O nó Remove Duplicates resolve o caso serial, mas não é atômico sob concorrência.
Para blindar de verdade, faça a dedupe no próprio banco: crie a coluna com UNIQUE constraint (ex.: stripe_event_id TEXT UNIQUE) e, logo após o Webhook, um nó Postgres com INSERT … ON CONFLICT (stripe_event_id) DO NOTHING RETURNING id. Se o RETURNING vier vazio, é reentrega duplicada → pare o fluxo ali (um IF checando se há linha retornada). O banco garante atomicidade, então mesmo duas entregas simultâneas: só uma vence o INSERT.
Duas coisas extras que reduzem muito o problema na origem: (1) responda 200 ao Stripe o mais rápido possível (use o Webhook em modo Respond Immediately ou um nó Respond to Webhook no início) — timeouts fazem o Stripe re-tentar e é aí que nascem muitas ‘duplicatas’; e (2) processe o pedido só depois do INSERT de controle passar. Assim o id do evento é sua chave de idempotência real, e a lógica de pedido nunca roda duas vezes.
Oi @Fatoki_Alfred
Stripe tenta novamente as entregas de webhook intencionalmente, portanto eventos duplicados são esperados. A abordagem recomendada é tornar seu fluxo de trabalho idempotente em vez de assumir que cada webhook é único.
A solução mais simples é armazenar o event.id do Stripe antes do processamento. No início do fluxo de trabalho:
- Extrair event.id.
- Consultar seu banco de dados (ou Data Store) para esse ID.
Se já existir, pare o fluxo de trabalho.
Caso contrário, salve o ID e continue o processamento.
Usar um nó IF antes de sua lógica de negócio geralmente é suficiente.
Se você estiver escrevendo para Airtable ou outro banco de dados, considere tornar event.id um campo único para que duplicatas sejam rejeitadas automaticamente.
Trate os webhooks do Stripe como entrega at-least-once e torne o workflow idempotente. Use o ID do evento Stripe como chave de idempotência. No início do workflow, verifique a assinatura do webhook e tente inserir esse ID de evento em uma tabela PostgreSQL com uma restrição única. Continue apenas se a inserção for bem-sucedida. Se o ID já existir, retorne uma resposta bem-sucedida e interrompa sem criar o pedido novamente.
Uma tabela simples pode conter event_id como chave primária mais status, received_at e completed_at. No nó Postgres, use um insert com ON CONFLICT DO NOTHING e retorne se uma linha foi inserida. Envie esse resultado para um nó IF. O ramo true processa o pedido e o ramo false sai. A restrição do banco de dados é importante porque duas cópias podem chegar próximas o suficiente para que uma verificação separada de lookup-then-insert deixe ambas passarem.
Marque o registro como processando quando reivindicado e completo apenas após o pedido ser bem-sucedido. Decida como os registros falhados devem ser tentados novamente para que um erro temporário não suprima permanentemente o evento. Também passe o ID do evento Stripe para sistemas downstream como chave de idempotência deles onde suportado. Não deduplicar por cliente, valor ou timestamp porque pagamentos legítimos separados podem compartilhar esses valores.
Crie uma chave de idempotência a partir de um ID de evento estável e armazene-a antes que os nós custosos sejam executados. Se a mesma chave chegar novamente, retorne antecipadamente e defina uma expiração que corresponda ao tempo que o remetente pode tentar novamente.