Fan-out e multicasting de webhooks
Quando um único evento de webhook precisa chegar a mais de um lugar, você tem um problema de fan-out. Um evento payment_intent.succeeded da Stripe precisa atualizar o seu serviço de pedidos, disparar um e-mail de confirmação e registrar no seu pipeline de analytics. Um webhook orders/create da Shopify precisa chegar ao seu sistema de fulfillment, ao seu CRM e ao seu controle de estoque. O evento é um. Os destinos são vários.
Fan-out, que é pegar um único evento recebido e distribuí-lo para múltiplos consumidores, é um dos padrões mais comuns em arquiteturas baseadas em webhooks e um dos mais propensos a erro quando implementado por conta própria. Este guia cobre o que é fan-out de webhooks, por que ele importa, abordagens comuns de implementação, os desafios que você vai encontrar pelo caminho e como um webhook gateway simplifica o problema.
O que é fan-out de webhooks?
Fan-out é o padrão de duplicar um único evento recebido e entregar cópias para vários destinos downstream, onde cada destino processa o evento de forma independente.
No contexto de webhooks, funciona assim: um serviço externo envia um único webhook para o seu sistema. O seu sistema precisa que vários serviços internos ajam sobre aquele evento, mas cada serviço tem o próprio endpoint, a própria lógica de processamento e as próprias características de falha. Fan-out é o mecanismo que leva uma requisição HTTP recebida a todos os lugares onde ela precisa chegar.
O termo relacionado "multicasting" descreve a mesma ideia sob a ótica de redes: enviar uma única mensagem para um grupo de receptores simultaneamente, em vez de ponto a ponto. Em infraestrutura de webhooks, "fan-out" e "multicasting" são na prática intercambiáveis.
Fan-out vs. roteamento
Fan-out e roteamento costumam ser discutidos juntos, mas resolvem problemas diferentes.
Roteamento é direcionar um evento para o destino correto com base no seu conteúdo. Um evento customers/create vai para o serviço de CRM. Um evento orders/create vai para o serviço de fulfillment. Um evento, um destino, escolhido por uma regra.
Fan-out é entregar um evento para múltiplos destinos simultaneamente. Um evento orders/create vai para o serviço de fulfillment, o serviço de notificações e o pipeline de analytics. Um evento, muitos destinos, todos recebendo.
Na prática, a maioria dos sistemas precisa dos dois. Uma origem Shopify pode emitir dezenas de tipos de evento. Alguns eventos precisam ser roteados para um único serviço. Outros precisam ser distribuídos para vários. E alguns precisam de fan-out condicional, em que o evento vai para um conjunto específico de destinos com base no conteúdo do payload. É a combinação de roteamento e fan-out que dá a você uma distribuição de eventos flexível e sustentável.
Por que o fan-out importa
O fan-out se torna importante assim que a sua arquitetura deixa de ser um monólito único consumindo todos os eventos de webhook. Veja quando você vai esbarrar nele.
Arquiteturas de microsserviços
Em uma aplicação monolítica, um endpoint recebe o webhook e a lógica interna da aplicação decide qual módulo o trata. Não existe problema de fan-out porque tudo vive no mesmo processo.
Assim que você decompõe em microsserviços, cada serviço é dono de um contexto delimitado e tem o próprio endpoint. Um único evento de webhook pode ser relevante para vários serviços, mas o provedor de webhook só o envia uma vez, para uma URL. Alguma coisa precisa copiar e distribuir esse evento.
Integrações com múltiplos sistemas
Muitos times precisam que um evento de webhook chegue não só aos serviços próprios, mas também a sistemas de terceiros. Um webhook de pedido da Shopify pode precisar chegar ao seu serviço de pedidos, ao ShipStation para fulfillment, ao Twilio para notificações por SMS e ao HubSpot para atualizações de CRM. Sem fan-out, você precisaria que o provedor de webhook suportasse múltiplas URLs por assinatura (a maioria não suporta) ou teria que construir lógica de encaminhamento customizada no endpoint que recebe.
Pipelines de dados e auditoria
Mesmo quando um único serviço cuida da lógica de negócio, você pode precisar de cópias dos eventos fluindo para sistemas de analytics, data warehouses ou logs de auditoria. O fan-out permite adicionar esses consumidores secundários sem alterar o seu pipeline principal de processamento.
Redundância e resiliência
Alguns times fazem fan-out para um destino de backup (um bucket S3 ou um armazenamento de dead letter) em paralelo ao serviço principal de processamento. Se o destino principal cair, o evento bruto fica preservado no backup, disponível para replay quando o serviço se recuperar.
Como implementar fan-out por conta própria
Se você está construindo fan-out do zero, existem várias abordagens, cada uma com seus trade-offs.
Abordagem 1: encaminhamento no nível da aplicação
A abordagem mais simples: o seu endpoint de webhook recebe o evento, processa e depois encaminha cópias para outros serviços internos via HTTP.
# Pseudocode: application-level fan-out
def handle_webhook(request):
event = verify_and_parse(request)
# Forward to each destination
for destination in get_destinations(event):
try:
http_post(destination.url, event.payload)
except Exception as e:
log_failure(destination, event, e)
Isso funciona em cenários pequenos, com poucos destinos. Os problemas aparecem rápido. O tempo de resposta do seu endpoint de webhook agora é a soma de todas as chamadas downstream. Se algum destino estiver lento, a cadeia inteira fica lenta, e muitos provedores de webhook têm janelas de timeout rígidas (o GitHub espera uma resposta em até 10 segundos, por exemplo). Se um destino estiver indisponível, você precisa de lógica de retry para cada chamada de encaminhamento. Se o seu próprio endpoint cair no meio do fan-out, alguns destinos receberam o evento e outros não, deixando o seu sistema em um estado inconsistente.
Abordagem 2: fan-out baseado em fila
Uma abordagem mais confiável usa uma fila de mensagens ou um sistema pub/sub como camada de fan-out.
O padrão: o seu endpoint de webhook faz a ingestão do evento, publica em um tópico ou exchange e retorna 200 imediatamente. Consumidores downstream assinam o tópico e processam os eventos de forma independente.
Na AWS, isso costuma ser implementado com o SNS (o broker de fan-out) entregando para várias filas SQS (uma por consumidor), com funções Lambda ou workers consumindo cada fila. No GCP, o equivalente é o Pub/Sub com múltiplas subscriptions. Opções self-hosted incluem exchanges do RabbitMQ ou tópicos do Kafka com consumer groups.
Isso resolve o problema de timeout: o seu endpoint de webhook responde imediatamente após publicar. Também desacopla os consumidores, já que cada um processa no próprio ritmo, e um consumidor lento não bloqueia os outros.
Mas introduz uma complexidade operacional significativa. Agora você gerencia um message broker, várias filas ou subscriptions, workers consumidores, dead letter queues para cada consumidor e monitoramento de tudo isso. Você precisa configurar o comportamento de retry, o visibility timeout e os limites de throughput de cada fila. Se estiver usando filas FIFO para garantias de ordenação, fica limitado a 300 mensagens por segundo por fila na AWS. E o próprio message broker vira uma dependência crítica. Se o SNS perder uma entrega para uma fila SQS (o que pode acontecer se a fila estiver indisponível), o evento é perdido para aquele consumidor.
Você também precisa construir a camada de gestão de assinaturas: quais consumidores devem receber quais eventos? Atualizar regras de roteamento significa mudar configuração de infraestrutura (subscriptions do SNS, políticas de filas SQS) em vez de código de aplicação.
Abordagem 3: serviço de fan-out customizado
Alguns times constroem um serviço interno dedicado que fica entre o endpoint de webhook e os consumidores downstream. Esse serviço recebe eventos, aplica regras de roteamento e fan-out vindas de um repositório de configuração e gerencia a entrega a cada destino com a própria lógica de retry, tratamento de dead letter e observabilidade.
Isso te dá o maior controle, mas é na prática construir o seu próprio webhook gateway. Times que seguem esse caminho acabam investindo bastante tempo de engenharia em infraestrutura que não é o produto principal deles, e ainda assim enfrentam os desafios de durabilidade, idempotência, monitoramento e escala que gateways feitos para isso já resolveram.
Desafios do fan-out DIY
Independentemente da abordagem escolhida, o fan-out traz vários problemas difíceis.
Falhas parciais de entrega
Quando um evento precisa chegar a cinco destinos e a entrega ao terceiro falha, você fica com um estado inconsistente. Três destinos processaram o evento. Dois não. O seu sistema precisa rastrear o status de entrega por destino, refazer as tentativas das falhas de forma independente e evitar reprocessamento nos destinos que já tiveram sucesso.
Isso é mais difícil do que a lógica de retry de um único destino, porque cada destino tem as próprias características de falha. Um pode estar temporariamente fora do ar. Outro pode estar aplicando rate limit em você. Um terceiro pode estar retornando erros 500 por causa de um bug não relacionado. Você precisa de filas de retry e estratégias de backoff independentes por destino.
Deduplicação entre destinos
Se o provedor de webhook refizer uma entrega (porque o seu endpoint demorou a responder), você vai receber o mesmo evento de novo. A sua camada de fan-out agora distribui essa duplicata para todos os destinos. Cada destino precisa lidar com deduplicação) de forma independente, ou a sua camada de fan-out precisa deduplicar antes de distribuir. As duas abordagens exigem rastrear IDs de evento e manter estado.
Ordenação e consistência
Se a ordem importa — e para muitos tipos de webhook ela importa (um payment_intent.succeeded deve ser processado antes de um charge.refunded da mesma transação) —, o fan-out complica ainda mais. A fila de cada destino pode processar eventos em ritmos diferentes, então um destino que ficou para trás pode processar eventos fora de ordem em relação a outro que está em dia. Garantir ordenação entre destinos de um fan-out é caro do ponto de vista arquitetural e raramente possível sem trade-offs de throughput.
Observabilidade entre destinos
Quando um evento é distribuído para cinco destinos, você precisa rastrear o ciclo de vida dele nos cinco de forma independente. O destino 3 recebeu? Quando? Processou com sucesso? Se falhou, quantos retries já foram tentados? Um único evento vira cinco traces de entrega, cada um com o próprio status, tempo e histórico de erros. Construir dashboards e alertas sobre essa matriz é bem mais complexo do que monitorar um único destino.
Escalar a camada de fan-out
Se você recebe 1.000 eventos por minuto e cada evento é distribuído para 5 destinos, a sua camada de fan-out precisa lidar com 5.000 operações de entrega por minuto, cada uma com o próprio comportamento de retry, timeout e tratamento de erro. O multiplicador do fan-out se aplica a todo componente downstream do ponto de ramificação: filas, workers, conexões de banco de dados e banda de rede.
Como a Hookdeck lida com fan-out
Um webhook gateway como o Hookdeck Event Gateway trata fan-out como uma primitiva de infraestrutura de primeira classe, e não como algo que você constrói em cima de ferramentas genéricas.
O modelo de connections
Na Hookdeck, o fan-out é uma consequência natural do modelo Source -> Connection -> Destination. Uma Source representa um provedor de webhook (Stripe, Shopify, GitHub). Um Destination é um endpoint que recebe eventos. Uma Connection liga uma Source a um Destination com regras opcionais (filtros, transformações, configuração de retry).
O fan-out acontece quando você cria várias Connections a partir da mesma Source. Quando a Hookdeck recebe um webhook na URL de uma Source, ela cria uma cópia do evento para cada Connection associada àquela Source e entrega cada cópia ao Destination correspondente de forma independente.
Não há nada a mais para configurar: nenhum message broker para provisionar, nenhuma gestão de assinaturas para construir. Você adiciona uma Connection e o novo destino começa a receber eventos.
Fan-out condicional com filtros
Fan-out bruto, mandando todo evento para todo destino, raramente é o que você quer. A maioria dos destinos se importa apenas com um subconjunto dos eventos de uma origem. As regras de filtro da Hookdeck permitem controlar exatamente quais eventos chegam a cada destino.
Cada Connection pode ter um filtro que casa com qualquer combinação de body do evento, headers e parâmetros de query. Eventos que não passam pelo filtro são silenciosamente descartados para aquela Connection, enquanto outras Connections da mesma Source seguem inalteradas.
Por exemplo, uma Source da Shopify pode ter três Connections:
- Connection para o serviço de fulfillment: filtra eventos
orders/createeorders/updated. - Connection para o serviço de notificações: filtra apenas eventos
orders/create. - Connection para o pipeline de analytics: sem filtro, recebe todos os eventos.
Isso te dá fan-out seletivo: cada destino recebe exatamente os eventos de que precisa, configurado de forma declarativa e sem mudanças de código.
Transformações por destino
Além da filtragem, cada Connection pode aplicar uma transformação ao payload do evento antes da entrega. Isso significa que o mesmo evento de origem pode ser entregue a destinos diferentes em formatos diferentes. O seu pipeline de analytics pode precisar do payload bruto. O seu serviço de notificações pode precisar de uma estrutura simplificada, só com o ID do pedido e o e-mail do cliente. O seu serviço de fulfillment pode precisar do payload enriquecido com campos específicos do armazém.
As transformações são aplicadas de forma independente por Connection, então mudar o formato de um destino nunca afeta outro.
Garantias de entrega independentes por destino
É aqui que a abordagem da Hookdeck mais se distancia do fan-out DIY. Cada cópia distribuída de um evento é entregue, retentada e rastreada de forma independente por destino. Se a entrega ao serviço de fulfillment falhar, a Hookdeck refaz a tentativa conforme as regras de retry daquela Connection, enquanto o serviço de notificações e o pipeline de analytics já receberam suas cópias com sucesso.
Cada destino tem a própria fila de entrega, o próprio backoff de retry e o próprio rate limit. Um destino lento ou com falhas não bloqueia nem afeta nenhum outro destino do fan-out.
Observabilidade completa dos eventos distribuídos
Cada cópia distribuída de um evento ganha o próprio trace de entrega no dashboard da Hookdeck. Você consegue ver quais destinos receberam o evento, quando cada tentativa de entrega aconteceu, qual resposta cada destino retornou e quais entregas estão aguardando retry.
A busca full-text permite encontrar um evento por qualquer campo do payload e ver o status de entrega dele em todos os destinos ao mesmo tempo. O rastreamento estruturado de issues abre issues quando entregas falham, para que o seu time investigue e resolva problemas por destino em vez de vasculhar logs de erro agregados.
Alertas por destino
Cada Connection pode disparar alertas de forma independente. Se o seu serviço de fulfillment parar de responder, o seu time recebe uma notificação no Slack ou um alerta no PagerDuty referente àquele destino específico. O pipeline de analytics, que segue processando normalmente, não gera ruído.
Padrões de fan-out na prática
Estes são padrões comuns que combinam fan-out com filtragem e roteamento.
Padrão 1: roteamento por tipo de evento com fan-out completo para eventos compartilhados
Alguns eventos são relevantes para um único serviço. Outros precisam de fan-out. Você pode modelar isso com regras de filtro sobrepostas.
Source: Stripe Connections:
- Para o serviço de pagamentos: filtra eventos
payment_intent.* - Para o serviço de assinaturas: filtra eventos
customer.subscription.* - Para o serviço de notificações: filtra
payment_intent.succeededecustomer.subscription.created(um subconjunto que se sobrepõe aos dois serviços acima) - Para o log de auditoria: sem filtro (recebe tudo)
O serviço de pagamentos e o de assinaturas recebem cada um os eventos do seu domínio. O serviço de notificações recebe os eventos que exigem comunicação com o cliente, um subconjunto que cruza fronteiras de domínio. O log de auditoria recebe tudo. Uma Source, quatro Connections, quatro visões filtradas diferentes do mesmo fluxo de eventos.
Padrão 2: processamento principal com backup
Distribua todo evento tanto para o seu endpoint principal de processamento quanto para um armazenamento de backup (S3, um banco secundário ou um serviço dedicado de arquivamento). Se o seu serviço principal tiver um bug que corrompe dados, os eventos brutos ficam preservados no backup, disponíveis para replay e reprocessamento.
Padrão 3: decomposição gradual em microsserviços
Ao extrair um serviço de um monólito, distribua os eventos relevantes tanto para o endpoint monolítico antigo quanto para o novo serviço. Rode os dois em paralelo, compare resultados e faça a virada para o novo serviço quando estiver confiante. Esse é o padrão strangler fig aplicado ao processamento de webhooks, e o fan-out no nível do gateway torna trivial montá-lo e desmontá-lo.
Conclusão
Fan-out é simples em conceito (um evento, muitos destinos), mas os detalhes de implementação em torno de confiabilidade, garantias de entrega independentes, observabilidade e tratamento de falhas se acumulam rápido quando você constrói tudo por conta própria.
Se as suas necessidades de fan-out são simples (dois destinos, baixo volume, entrega best-effort), o encaminhamento no nível da aplicação pode bastar. Mas conforme você adiciona destinos, precisa de lógica de retry por destino, exige visibilidade do status de entrega entre consumidores ou quer mudar o roteamento sem fazer novo deploy de código, um webhook gateway feito para isso elimina o peso da infraestrutura e permite gerenciar o fan-out de forma declarativa.
Para um passo a passo de como configurar fan-out com a Hookdeck, veja o guia de fan-out. Para configuração de filtragem e roteamento, veja o guia de filtragem e roteamento de eventos.
Infraestrutura de webhooks, gerenciada para você
A Hookdeck cuida da ingestão, entrega, observabilidade e recuperação de erros — para que você não precise.