image

Bootcamps ilimitados e +750 cursos pra sempre

70
%OFF
Eduardo Assis
Eduardo Assis15/08/2026 16:35
Compartilhe
IBM Bob: IA de Nível Empresarial para Desenvolvedores e Tech LeadersRecomendados para vocêIBM Bob: IA de Nível Empresarial para Desenvolvedores e Tech Leaders

Webhooks para quem está começando: cuidados essenciais para não perder a confiabilidade da aplicação

    Quando começamos a trabalhar com webhooks, o fluxo parece simples:

    um sistema envia um evento → nossa aplicação recebe → processamos → atualizamos o banco.

    Na prática, sistemas distribuídos são menos previsíveis.

    Um webhook pode chegar mais de uma vez, atrasado, fora de ordem ou ser processado simultaneamente por diferentes workers.

    Isso acontece em integrações com WhatsApp, gateways de pagamento, e-commerce, logística, e-mail, ERPs e praticamente qualquer sistema baseado em eventos assíncronos.

    Por isso, receber um evento não significa necessariamente que ele deve alterar o estado atual da aplicação.

    Eventos podem chegar fora de ordem

    Imagine os status de uma mensagem enviada pelo WhatsApp:

    sent
    ↓
    delivered
    ↓
    read
    

    O fluxo esperado é simples: a mensagem foi enviada, chegou ao dispositivo do destinatário e foi lida.

    Mas os webhooks podem ser recebidos nesta ordem:

    sent
    read
    delivered
    

    Se a aplicação atualizar o campo status sempre que receber um webhook, teremos o seguinte resultado:

    status = read
    

    Depois, quando o evento atrasado chegar:

    status = delivered
    

    Agora, uma mensagem que já foi lida aparece apenas como entregue.

    O estado regrediu.

    Isso pode acontecer porque um evento ficou preso em uma fila, porque o provedor realizou uma nova tentativa de entrega ou porque diferentes workers processaram eventos simultaneamente.

    A menos que o contrato do provedor ofereça garantias específicas, não devemos presumir que os eventos serão:

    • entregues uma única vez;
    • recebidos na ordem em que aconteceram;
    • processados por apenas um worker;
    • processados imediatamente;
    • processados sem concorrência.

    Uma integração confiável precisa continuar correta mesmo quando essas condições não são ideais.

    Três proteções fundamentais

    Uma integração robusta normalmente combina pelo menos três mecanismos: idempotência, controle das transições de estado e atualizações atômicas.

    1. Idempotência

    Idempotência significa que processar novamente a mesma operação produz o mesmo resultado observável, sem duplicar seus efeitos.

    Imagine que recebemos:

    event_id = abc123
    status = delivered
    

    Por não receber uma resposta a tempo, o provedor envia o mesmo evento novamente.

    Sem proteção, a aplicação poderia:

    • executar automações novamente;
    • duplicar métricas;
    • enviar notificações repetidas;
    • gerar cobranças;
    • registrar efeitos colaterais indesejados.

    Uma estratégia comum é armazenar o identificador externo do evento em uma tabela com índice UNIQUE.

    Ao receber novamente o evento abc123, a aplicação reconhece que ele já foi tratado e não repete seus efeitos.

    Entretanto, apenas verificar o event_id na aplicação não é suficiente. Dois workers poderiam fazer a consulta ao mesmo tempo e concluir que o evento ainda não existe.

    A proteção precisa estar no banco.

    Também é importante que o registro da idempotência e a alteração do estado sejam consistentes. Se a aplicação marcar o evento como processado e falhar antes de executar a atualização principal, esse evento poderá nunca mais ser tratado.

    Quando possível, essas operações devem acontecer na mesma transação. Em arquiteturas mais complexas, padrões como transactional inbox e outbox também ajudam a resolver esse problema.

    2. Controle das transições de estado

    Agora imagine dois eventos legítimos e diferentes:

    event_id = 100
    status = read
    

    e:

    event_id = 101
    status = delivered
    

    A idempotência não deve bloquear nenhum deles, pois possuem identificadores diferentes.

    O problema é que o segundo evento representa um estado anterior.

    Em fluxos lineares, podemos usar transições monotônicas: o estado pode avançar, mas não regressar.

    Por exemplo:

    sent       = 1
    delivered  = 2
    read       = 3
    

    Se o estado atual for read, um evento delivered não deve sobrescrevê-lo.

    Assim:

    sent → delivered
    delivered → read
    

    são transições permitidas.

    Enquanto:

    read → delivered
    delivered → sent
    

    devem ser ignoradas.

    Essa abordagem funciona bem quando existe uma progressão claramente linear.

    Nem todo domínio, porém, pode ser representado apenas com uma sequência numérica.

    Pagamentos, pedidos e entregas podem possuir estados como:

    failed
    cancelled
    refunded
    chargeback
    returned
    

    Nesses casos, é mais seguro modelar uma máquina de estados, definindo explicitamente quais transições são permitidas.

    Um pagamento paid, por exemplo, não deveria voltar para pending por causa de um webhook atrasado. Mas ele poderia avançar legitimamente para refunded ou chargeback.

    Portanto, a regra correta depende do domínio:

    • fluxos lineares podem usar ranks monotônicos;
    • fluxos ramificados precisam de transições explicitamente permitidas;
    • alguns eventos podem exigir timestamps, versões ou sequências fornecidas pelo provedor.

    3. Atualizações atômicas

    Ainda existe o problema da concorrência.

    Imagine dois workers processando simultaneamente:

    Worker A → delivered
    Worker B → read
    

    Uma implementação ingênua poderia fazer:

    SELECT status
    

    Depois comparar o resultado na aplicação e executar:

    UPDATE messages
    

    Os dois workers podem ler o mesmo estado antes que qualquer atualização seja concluída. Isso cria uma condição de corrida.

    Por isso, a validação da transição deve participar da própria atualização no banco.

    Se a tabela armazenar tanto o status quanto seu rank, podemos fazer conceitualmente:

    UPDATE messages
    SET
      status = 'read',
      status_rank = 3
    WHERE id = ?
    AND status_rank < 3;
    

    O banco atualizará o registro somente se o novo estado representar um avanço.

    A quantidade de linhas alteradas também informa se a transição foi aplicada ou descartada.

    Isso é mais seguro do que depender apenas de uma comparação na aplicação:

    if ($newStatus->rank() > $currentStatus->rank()) {
      // atualizar
    }
    

    Entre o if e o UPDATE, outro processo poderia modificar o registro.

    Dependendo do fluxo, também podemos utilizar:

    • transações;
    • locks;
    • updates condicionais;
    • índices únicos;
    • constraints;
    • controle otimista por versão.

    A aplicação expressa a regra de negócio, enquanto o banco ajuda a garantir sua consistência.

    Um exemplo em Laravel

    Podemos começar representando a ordem dos estados:

    enum MessageStatus: string
    {
      case Sent = 'sent';
      case Delivered = 'delivered';
      case Read = 'read';
    
      public function rank(): int
      {
          return match ($this) {
              self::Sent => 1,
              self::Delivered => 2,
              self::Read => 3,
          };
      }
    }
    

    Depois, fazemos uma atualização condicional:

    $newStatus = MessageStatus::from($payload['status']);
    
    $updated = Message::query()
      ->whereKey($messageId)
      ->where('status_rank', '<', $newStatus->rank())
      ->update([
          'status' => $newStatus->value,
          'status_rank' => $newStatus->rank(),
      ]);
    

    Se $updated for igual a zero, o registro já estava no mesmo estado ou em um estado mais avançado.

    A comparação acontece dentro do UPDATE, reduzindo o risco de concorrência entre workers.

    Ainda será necessário combinar essa proteção com idempotência, transações e regras adequadas ao domínio.

    Idempotência, ordenação e atomicidade resolvem problemas diferentes

    Esse ponto merece atenção.

    Considere:

    Evento A
    id = 10
    status = delivered
    

    e:

    Evento B
    id = 11
    status = read
    

    São eventos diferentes.

    Se chegarem na ordem esperada, tudo funciona:

    delivered
    read
    

    Mas eles também podem chegar assim:

    read
    delivered
    

    A idempotência não deve eliminar o segundo evento, pois seu identificador é diferente.

    É a regra de transição que deve impedir:

    read → delivered
    

    Em resumo:

    • Idempotência protege contra efeitos duplicados.
    • Regras de transição protegem contra eventos fora de ordem.
    • Atomicidade protege contra condições de corrida.
    • Transações ajudam a manter consistentes o registro do evento e seus efeitos.

    São problemas relacionados, mas diferentes.

    Responder rápido não significa responder antes de salvar

    Endpoints de webhook devem responder rapidamente para evitar que o provedor considere a entrega como falha e faça novas tentativas.

    Mas existe um detalhe importante: a aplicação não deveria retornar sucesso antes de armazenar o evento de forma durável.

    Um fluxo mais seguro é:

    receber
    → validar assinatura
    → validar estrutura básica
    → persistir em fila ou tabela confiável
    → responder ao provedor
    → processar assincronamente
    

    Se a aplicação responder 200 OK e falhar antes de salvar o evento, o provedor poderá considerá-lo entregue e não enviá-lo novamente.

    O processamento assíncrono também precisa ser seguro caso o job seja executado mais de uma vez.

    O que pode acontecer sem essas proteções?

    Em uma plataforma de atendimento, por exemplo, podem surgir:

    • mensagens lidas aparecendo apenas como entregues;
    • métricas incorretas;
    • dashboards inconsistentes;
    • automações executadas com estados antigos;
    • notificações duplicadas;
    • contadores divergentes;
    • cobranças repetidas;
    • comportamentos diferentes conforme a ordem de processamento;
    • bugs difíceis de reproduzir.

    Talvez o pior seja que o sistema pode funcionar durante semanas sem nenhum problema aparente.

    Até que uma combinação específica de latência, retries, filas e concorrência revele o erro.

    Esses bugs dependem de timing. Por isso, costumam ser difíceis de diagnosticar em ambiente local.

    Uma mudança de mentalidade

    Ao desenvolver integrações por webhook, precisamos deixar de pensar apenas:

    Recebi um evento, então vou atualizar o registro.

    E começar a perguntar:

    A origem deste evento é autêntica?
    O payload é válido?
    Este evento já produziu seus efeitos?
    Ele representa uma transição permitida?
    Ainda faz sentido diante do estado atual?
    A atualização está protegida contra concorrência?

    Essa mudança separa uma integração que funciona apenas em condições ideais de uma integração preparada para ambientes reais.

    Checklist para webhooks em produção

    Antes de considerar uma integração pronta, vale verificar:

    • A assinatura ou autenticidade do webhook é validada?
    • Existe um identificador único para cada evento?
    • Esse identificador possui proteção UNIQUE no banco?
    • O processamento é idempotente?
    • Eventos fora de ordem podem regredir o estado?
    • As transições permitidas estão explícitas?
    • Atualizações críticas são atômicas?
    • O evento é persistido antes da resposta de sucesso?
    • Processamentos demorados são enviados para filas?
    • Os retries utilizam backoff e, quando necessário, jitter?
    • Existe tratamento para falhas permanentes ou uma dead-letter queue?
    • Jobs são seguros caso sejam executados novamente?
    • Logs evitam expor dados sensíveis?
    • Existem métricas e alertas para falhas e eventos descartados?
    • Existem testes para duplicidade, desordem e concorrência?

    Conclusão

    Webhooks parecem simples quando olhamos apenas para o HTTP:

    POST → receber JSON → atualizar banco
    

    Mas uma integração realmente confiável exige pensar em sistemas distribuídos.

    Eventos podem ser duplicados, atrasados, recebidos fora de ordem ou processados simultaneamente.

    Por isso, alguns conceitos devem estar entre os primeiros aprendizados de quem trabalha seriamente com webhooks:

    • Idempotência: impede efeitos duplicados.
    • Regras de transição: evitam alterações inválidas ou regressões de estado.
    • Atualizações atômicas: protegem contra concorrência.
    • Persistência durável: evita confirmar eventos que ainda podem ser perdidos.

    Não é excesso de engenharia.

    É uma das bases para construir integrações assíncronas confiáveis — especialmente quando estamos lidando com pagamentos, mensagens, pedidos, logística ou automações.

    Compartilhe
    Recomendados para você
    Nublify - Primeiros passos em IA e Cloud
    IBM Bob: IA de Nível Empresarial para Desenvolvedores e Tech Leaders
    AWS - Agentes de IA em Campo
    Comentários (0)
    Recomendados para vocêIBM Bob: IA de Nível Empresarial para Desenvolvedores e Tech Leaders