Shopping cart

Subtotal $0.00

View cartCheckout

Building better devs

TnewsTnews
  • Home
  • Artigos
  • O mito do exactly-once: por que seus webhooks mentem para você
Artigos

O mito do exactly-once: por que seus webhooks mentem para você

Email : 5

O mito do exactly-once: por que seus webhooks mentem para você

Se você acha que um evento chega uma vez, processa uma vez e vai embora — boas notícias: a realidade não liga para a sua fé. Na borda da internet, entre plataformas de pagamento, lojas e mensagerias, o “exactly-once” é um unicórnio simpático que não paga boleto. O que existe, quase sempre, é at-least-once com cara de educação: cabe a você fingir que nada repetiu.

O problema fica caro rápido. Um checkout que repete confirmações dispara duas etiquetas de envio, dobra estoque reservado e gera um customer support digno de Black Friday às 3h da manhã. E, ironicamente, tudo começou porque alguém colocou um retry automático com boas intenções e más premissas.

Quando a rede faz de conta

Plataformas que você provavelmente usa já assumem a duplicação como fato da vida. O Slack Events API entrega eventos no modelo at-least-once, pede um 2xx em até 3 segundos e avisa que duplicatas podem rolar; dedupe por event_id por alguns minutos e siga. A Stripe recomenda processamento idempotente de webhooks, porque o mesmo event.id pode bater na sua porta mais de uma vez — e sim, ela reenvia por até dias quando seu endpoint engasga. A Shopify também reenvia webhooks por até 48 horas em backoff exponencial, assina cada payload com HMAC e deixa o resto com você.

Do lado das filas, a história não muda muito. O Amazon SQS FIFO oferece ordenação e deduplicação por janela de 5 minutos (content-based ou com um message deduplication id). É ótimo para reduzir tempestades de repetição, mas não é exactly-once: existe visibilidade, reentrega, e o “uma vez” morre na primeira partição em rede.

Idempotência não é um cabeçalho, é um contrato

Idempotência é o paraquedas que você costura caindo. O padrão funciona quando você transforma uma operação potencialmente destrutiva em uma função determinística com chave. Em APIs, a Stripe popularizou o Idempotency-Key no request: o servidor guarda a primeira resposta associada àquela chave e devolve o mesmo resultado em repetições. O detalhe que morde é operacional:

Primeiro, a chave precisa estar ancorada em algo que o negócio reconhece — carrinho, sessão, ou uma composição que evite surpresas. Segundo, você precisa registrar a chave com um unique index atômico e salvar o resultado junto com um payload hash. Se a mesma chave vier com corpo diferente, não é retry inocente: é bug, abuso ou corrida. Terceiro, a janela de retenção tem que respeitar o comportamento humano e da rede: TTL de 24–72h normalmente cobre o festival de recarregar a página, alternar 4G e cair o Wi‑Fi do escritório.

Idempotência para webhooks inverte a direção mas mantém o enredo: dedupe por event_id (Stripe), delivery_id (GitHub) ou payload hash + source (Shopify) em um armazenamento consistente. Guarde status (seen, processing, done), timestamps e resultado. Reentradas durante races pedem mutex por chave ou uma fila por partição lógica — guardar só o “vi isso” sem bloquear o processamento abre flancos para duas execuções simultâneas do mesmo evento.

Exactly-once dentro, maybe lá fora

No reino das filas transacionais, há progresso real. O Apache Kafka introduziu idempotent producers e transações, permitindo exactly-once semantics (EOS) entre tópicos — o que reduz o famoso “quase exatamente uma vez” a algo mais confiável. Com EOS, você produz e consome em uma transação, evitando duplicação entre estágios. Só que o fim da linha raramente é outro tópico: é um banco, um ledger, um serviço externo com o próprio humor. A promessa só se sustenta se o sink também for idempotente. Sem isso, EOS vira um oásis local em um deserto de reentregas.

Para cruzar o abismo, o padrão outbox ajuda: você grava a mutação de domínio e o evento de integração na mesma transação do banco. Um relayer (CDC com Debezium, por exemplo) publica na fila. Do outro lado, consumidores deduplicam por chave de negócio e escrevem de forma idempotente (UPSERT, MERGE, ou operações em tabelas com unique constraints). Não é bonito, mas é auditável, reprocessável e, principalmente, previsível.

Contabilidade técnica: o custo da ilusão

Duplicação parece detalhe até virar planilha. Em e-commerce, 0,1% de eventos de pedido processados em duplicidade numa base de 5 milhões/mês dá cinco mil ocorrências para o SAC, estoque desalinhado e reconciliação financeira ruindo. Cada reembolso custa taxa, atrito e probabilidade de chargeback. Em SaaS B2B, duplicar execuções de tarefas pesadas infla infraestrutura, distorce métricas de uso e sabota previsões de receita.

Tem mais: auditorias regulatórias não aceitam “foi a rede”. Sem rastreabilidade clara — quem processou, quando, com qual chave e qual decisão — o risco de não conformidade vira custo recorrente. E as equipes de dados herdam o caos: pipelines consomem eventos em duplicidade, métricas derivadas inflacionam e executivos tomam decisões sobre fantasmas.

Trade-offs que doem menos do que o chargeback

Alguns acordos com a realidade pagam dividendos:

– Escolha chaves idempotentes de negócio: order_id + versão de estado é mais resiliente do que um UUID aleatório por tentativa. A chave deve dizer “esta operação específica” em termos do domínio, não do transporte.

– Centralize dedupe em um armazenamento rápido e consistente por partição de chave. Uma tabela com unique index e colunas de status já resolve 80% dos casos; Redis com SETNX + TTL cobre janelas curtas, mas cuidado com persistência e failover.

– Projete janelas realistas. A janela de dedupe do SQS FIFO é 5 minutos; ótimo para rafagas, insuficiente para fluxos de pagamento ou logística. Monte sua própria janela no lado consumidor.

– Use DLQ sem vergonha. Mensagens venenosas precisam sair do caminho para o resto fluir. Reprocessamento controlado e playbooks de correção valem mais que insistir em heróis silenciosos no consumo principal.

– Habilite replays como recurso de produto. Guardar decisão + entrada permite reexecutar sem dano. Se reprocessar muda o resultado, você não tem determinismo, tem sorte.

Casos de campo que educam

Uma fintech média viu duplicidade cair de 0,3% para 0,01% quando moveu dedupe de um cache ad hoc para uma tabela com unique constraint e bloqueio por chave, coalescendo retries em filas por partição de cliente. A latência P95 subiu 12 ms; o custo de suporte caiu 38% em três meses.

Um marketplace eliminou estornos em lote ao transformar o “pagamento confirmado” em uma inserção única em um ledger com natural key (payment_provider_id + provider_event_id). O webhook da Stripe ainda repetia, mas registrar o mesmo evento duas vezes virou impossível por construção. Replays para conciliação passaram a ser rotineiros e sem medo.

Já uma edtech que dependia de webhooks do Slack para provisionar salas enfrentou tempestade de duplicatas durante picos. Resolveram parando de confiar no transporte: guardaram o event_id por 24h, responderam 2xx imediato e postergaram o trabalho para um worker com fila FIFO. Duplicatas viravam no-ops. A equipe parou de caçar fantasmas em logs.

O que chamamos de confiável

Confiabilidade não é prometer o impossível, é tornar o previsível entediante. O mundo fora do seu processo sempre vai te mandar o mesmo evento duas vezes — às vezes três. É estatística com personalidade. A resposta madura não é lutar por exactly-once na fronteira, e sim padronizar idempotência como camada: chaves de negócio, armazenamento com garantias, execução determinística e replays como cidadania plena.

O bônus inesperado? Quando você para de discutir “foi duplicado ou não” e passa a discutir “qual foi a decisão e por quê”, surgem produtos mais claros, auditorias mais rápidas e times menos reativos. O resto é rede sendo rede — e você dormindo melhor.

Related Tags:

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Posts