ArquiteturaIniciante6 min de leitura6 seções2007, Data aproximada

Webhooks

Em vez de perguntar 'já aconteceu?' de minuto em minuto, você registra uma URL e o outro sistema te chama quando acontece. É o Observer atravessando a internet — com tudo que a rede cobra por isso: assinatura, retry e duplicata.

Em 10 segundos

Você deixa uma URL registrada e o outro sistema te chama quando o evento acontece — o contrário de ficar perguntando. O preço: seu endpoint vira porta pública, e precisa verificar assinatura, engolir duplicatas e responder 200 antes de processar.

Antes de ter nome

É o Observer levado à fronteira entre sistemas: em vez de um objeto notificar outro no mesmo processo, um servidor faz um POST no outro pela rede.

  • #integracao
  • #eventos
  • #http
  • #notificacao
  • #polling
Analogia

A campainha em vez do 'já chegou?'

Você não liga para a pizzaria a cada dois minutos perguntando se a pizza saiu — deixa o endereço e o entregador toca a campainha. O endereço é a URL do webhook; a campainha é o POST. E como todo mundo pode tocar uma campainha, você olha pelo olho mágico antes de abrir: essa é a assinatura.

01Conceito

O problema: perguntar até a resposta mudar

Polling é a solução ingênua para 'quero saber quando X acontecer': pergunte sempre. O custo cresce dos dois lados — você paga requests inúteis e rate limit; o provedor paga carga de consultas cuja resposta não mudou — e a notícia ainda chega atrasada pelo intervalo do loop.

Webhook resolve latência e desperdício num golpe, mas muda a natureza do seu sistema: agora existe um endpoint público seu que terceiros chamam, e tudo que vale para uma API pública (autenticidade, disponibilidade, idempotência) passa a valer para ele.

O contrato implícito do emissor sério: ele assina o corpo (HMAC com segredo compartilhado), reenvia com backoff quando você não responde 2xx a tempo, e desiste depois de N tentativas — indo parar numa fila de mortos que alguém precisa olhar. Entrega é ao-menos-uma-vez e sem garantia de ordem: 'pagamento.confirmado' pode chegar antes de 'pagamento.criado'.

Por isso os quatro deveres do receptor: verificar a assinatura (o corpo sem ela é só uma história), deduplicar pelo id do evento, enfileirar e responder 200 imediatamente (processar inline estoura o timeout do emissor, que reenvia, que duplica), e tratar o webhook como notificação — a fonte da verdade é a API do provedor, consultada na conciliação periódica.

Como se encaixa
  1. Provedor (PSP)
  2. POST assinado (HMAC)Seu endpoint
  3. valida, deduplica, 200 jáFila interna
  4. processa no seu ritmoWorker
O endpoint faz o mínimo síncrono possível — validar, deduplicar, enfileirar — e devolve 200 em milissegundos; o trabalho de verdade acontece depois, no worker.
02Anatomia

As três pontas da entrega

Camada 1/3Emissor

Camada 1

Emissor

assina, tenta de novo, desiste com registro

Do lado de quem envia: assinar o corpo com HMAC + timestamp (anti-replay), reenviar com backoff exponencial quando não vier 2xx, e ter uma fila de mortos com visibilidade — webhook que falhou para sempre é incidente do cliente, não log perdido.

Se violar: emissor sem retry transforma qualquer instabilidade do receptor em evento perdido para sempre — e sem assinatura, qualquer um forja um 'pagamento aprovado'.

03Implementação

Código

import { createHmac, timingSafeEqual } from "node:crypto";

// O contrato de um receptor de webhook decente:
// 1. verificar a assinatura  2. deduplicar  3. enfileirar  4. responder 200 JÁ
const processados = new Set<string>();      // Redis/tabela na vida real
const fila: unknown[] = [];                 // SQS/RabbitMQ na vida real

function verificarAssinatura(corpo: string, assinatura: string, segredo: string) {
  const esperada = createHmac("sha256", segredo).update(corpo).digest("hex");
  // comparação em tempo constante — timing attack não revela a assinatura
  return (
    esperada.length === assinatura.length &&
    timingSafeEqual(Buffer.from(esperada), Buffer.from(assinatura))
  );
}

async function receberWebhook(req: {
  corpo: string;
  cabecalhos: Record<string, string>;
}): Promise<{ status: number }> {
  // 1. sem assinatura válida, o corpo é só uma história que alguém contou
  if (!verificarAssinatura(req.corpo, req.cabecalhos["x-assinatura"], SEGREDO)) {
    return { status: 401 };
  }

  const evento = JSON.parse(req.corpo) as { id: string; tipo: string };

  // 2. entrega é ao-menos-uma-vez: o mesmo evento VAI chegar de novo
  if (processados.has(evento.id)) return { status: 200 }; // duplicata: ok e tchau

  // 3. + 4. guarda para processar depois e responde rápido —
  // processar inline estoura o timeout do emissor, que reenvia, que duplica...
  processados.add(evento.id);
  fila.push(evento);
  return { status: 200 };
}

const SEGREDO = process.env.WEBHOOK_SECRET ?? "";
04No mundo real

Casos de uso

Confirmação de pagamento PIX

Cenário

O checkout gera um QR Code e precisa liberar o pedido no instante em que o cliente paga — esperar um polling de 30s mata a experiência no caixa.

Como o padrão entra

O PSP chama seu webhook com 'pix.confirmado' assinado. O endpoint valida o HMAC, deduplica pelo id da transação, enfileira e responde 200; o worker libera o pedido e avisa o front por WebSocket. Uma conciliação horária consulta a API do PSP e pesca qualquer evento que a rede tenha engolido.

Trade-off: Você ganhou latência de segundos, mas agora opera um endpoint público de missão crítica — com monitoração, janela de manutenção coordenada e a conciliação como rede de segurança obrigatória.

CI disparado por push

Cenário

A cada push no repositório, a esteira de build precisa começar imediatamente — clonar o repo de minuto em minuto para 'ver se mudou' não escala para centenas de projetos.

Como o padrão entra

O GitHub chama o webhook do servidor de CI com o evento de push assinado. O CI valida, enfileira o build e responde — o pipeline começa dois segundos depois do git push, e o custo de polling de centenas de repositórios simplesmente desaparece.

Trade-off: Um webhook perdido = build que nunca rodou; por isso os servidores de CI sérios mantêm um resync periódico comparando o último commit conhecido com o real.

05Cuidado

Armadilhas

  1. Processar dentro do handler

    O emissor dá 10 segundos e você gasta 30 processando: timeout, retry, e o mesmo evento processado duas vezes em paralelo. O handler síncrono valida, deduplica, enfileira e responde — o resto é trabalho do worker.

  2. Confiar no corpo sem verificar a assinatura

    Um endpoint público que aceita qualquer POST como verdade permite que qualquer um 'aprove' o próprio pagamento com um curl. A assinatura HMAC sobre o corpo cru é o que separa notificação de terceiro confiável de entrada de usuário anônimo.

  3. Assumir entrega única e em ordem

    Retry gera duplicata; rede gera desordem. Sem dedupe por id, o mesmo pagamento credita duas vezes; sem tolerância a desordem, o 'confirmado' que chegou antes do 'criado' quebra o fluxo. Os dois são o caso normal, não o excepcional.

  4. Webhook como fonte da verdade

    Webhook é notificação de melhor esforço: emissores perdem eventos, DLQs transbordam, endpoints ficam fora do ar. Quem decide estado de dinheiro é a consulta à API do provedor — a conciliação periódica não é paranoia, é o contrato.

06Decisão

Quando usar × quando evitar

Use quando

3
  • Notificação de eventos de terceiros com latência importante — pagamento, logística, mensageria.
  • Substituir polling caro contra APIs com rate limit.
  • Encadear sistemas seus via eventos HTTP quando uma fila compartilhada não é opção.

Evite quando

3
  • Receptores que não podem expor URL pública (apps móveis, desktop) — use push/polling.
  • Fluxos que exigem garantia forte de entrega e ordem — fila com offset faz isso melhor.
  • Como única fonte de estado financeiro — sem conciliação, cedo ou tarde diverge.

Testar o que ficou

4 armadilhas de Webhooks viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • webhooks do StripeEm vez de você ficar perguntando, o Stripe faz um POST no seu endpoint quando a cobrança muda de estado — a notificação chega sozinha.
  • webhooks do GitHubUm push no repositório dispara uma chamada HTTP para o serviço de CI, que começa o build sem ninguém consultar o GitHub em loop.
  • callback de gateway de pagamentoO provedor avisa a sua loja de forma assíncrona quando o Pix cai, fechando o pedido sem polling do lado de cá.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Eles chamam você quando o fato acontece.
app.post("/hooks/stripe", verificarAssinatura, handler);

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Você passa a manter um endpoint público, com verificação de assinatura e proteção contra reenvio
  • A entrega é do provedor e pode falhar, repetir ou chegar fora de ordem — o consumidor precisa ser idempotente

Não vale se: você controla os dois lados no mesmo processo — aí uma chamada direta ou um evento interno dispensa a rede.

Onde isto aparece

Continue por aqui