Skip to main content

Webhooks (Assíncronos)

PARA AGENTES DE IAChatGPTClaude

Padrão arquitetural de interação assíncrona entre serviços (Event-driven Push), no qual o provedor de eventos envia uma requisição HTTP POST com dados para a URL registrada do consumidor quando ocorre um evento sistêmico.

1. Visão Geral da Conceção e o Problema Sistémico

A integração tradicional de sistemas via polling constante (Polling / Short Polling) sofre de dois problemas mutuamente exclusivos:

  1. Alta latência de resposta: Se o cliente verifica alterações a cada 60 segundos, os dados são entregues com um atraso médio de 30 segundos.
  2. Desperdício colossal de recursos computacionais: 99% das requisições HTTP retornam respostas vazias 304 Not Modified ou [], sobrecarregando o banco de dados e a stack de rede.

Webhooks (Chamadas de retorno via HTTP) mudam o paradigma de Pull para Push. Em vez de perguntar: "Há novos dados?", o consumidor registra sua URL (Webhook Listener). Assim que um evento ocorre no sistema do provedor (transação Stripe bem-sucedida, novo commit no GitHub, mensagem recebida no Telegram), o provedor envia uma requisição HTTP POST com payload JSON diretamente para o servidor do assinante.

Polling Tradicional (Desperdício de Recursos):
Cliente  ---> GET /events?since=... ---> Servidor (Vazio: [])
Cliente  ---> GET /events?since=... ---> Servidor (Vazio: [])
Cliente  ---> GET /events?since=... ---> Servidor (Novo evento!)

Webhook Orientado a Eventos (Push Instantâneo):
Provedor de eventos (Stripe / GitHub / Telegram)
       |
       |  Evento: charge.succeeded (Payload do Evento + Assinatura HMAC)
       |  HTTP POST https://api.mysite.com/webhooks/stripe
       v
Consumidor (Validação da assinatura -> 200 OK -> Fila em segundo plano RabbitMQ/Redis)

2. Taxonomia Arquitetural e Modelo Mental

O ciclo de vida de um webhook apoia-se em três pilares arquiteturais:

  1. Autenticação Criptográfica (Assinatura HMAC):
    • O provedor gera um hash criptográfico do corpo da requisição (raw body) usando uma chave secreta compartilhada (Webhook Secret), adicionando-o ao cabeçalho (por exemplo, Stripe-Signature ou X-Hub-Signature-256).
    • O consumidor calcula o mesmo hash e valida-o, eliminando o risco de falsificação de requisições por atacantes.
  2. Idempotência e Deduplicação (Motor de Idempotência):
    • Cada evento possui um ID globalmente único (evt_xxxxxxxxxx).
    • O consumidor verifica atomicamente a existência do ID no cache/BD (por exemplo, via INSERT ... ON CONFLICT DO NOTHING ou Redis SET key NX EX 86400).
  3. Política de Entrega Garantida e Retentativas (Retry & Dead-Letter Queue):
    • Em caso de indisponibilidade do servidor do consumidor, o provedor repete as requisições seguindo uma escala exponencial (Exponential Backoff com jitter): após 5 min, 30 min, 2 horas, 12 horas.
    • Eventos que não puderam ser processados após o número máximo de tentativas são direcionados para a Dead-Letter Queue (DLQ) para análise manual pelos engenheiros.

3. Pipeline Técnico e Mecânica Interna

Processador de Produção com Verificação HMAC (Node.js / Express / TypeScript)

[!IMPORTANT] Para verificar a assinatura, é necessário obter o corpo bruto da requisição (raw buffer), e não o objeto parseado req.body; caso contrário, o hash não coincidirá devido a divergências nos espaços em branco e na ordenação das chaves JSON.

import express, { Request, Response } from "express";
import crypto from "crypto";

const app = express();

// Отримання raw buffer для ендпоінтів вебхуків
app.post(
  "/api/v1/stripe-webhook",
  express.raw({ type: "application/json" }),
  async (req: Request, res: Response) => {
    const signature = req.headers["stripe-signature"] as string;
    const secret = process.env.STRIPE_WEBHOOK_SECRET!;

    if (!signature) {
      return res.status(400).send("Missing signature header");
    }

    // 1. Криптографічна валідація підпису через HMAC SHA-256
    const computedHmac = crypto
      .createHmac("sha256", secret)
      .update(req.body)
      .digest("hex");

    // Безпечне порівняння за фіксований час
    const isValid = crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(computedHmac)
    );

    if (!isValid) {
      return res.status(401).send("Invalid signature");
    }

    const payload = JSON.parse(req.body.toString());
    const eventId = payload.id;

    // 2. Ідемпотентна перевірка дедуплікації
    const isNew = await redis.set(`webhook:processed:${eventId}`, "1", "NX", "EX", 86400);
    if (!isNew) {
      // Подію вже оброблено — повертаємо 200 OK без повторного виконання
      return res.status(200).json({ status: "already_processed" });
    }

    // 3. Відправка важкої роботи в чергу BullMQ
    await eventQueue.add("process_payment", payload);

    // 4. Миттєве підтвердження отримання
    return res.status(200).json({ received: true });
  }
);

4. Cenários de Engenharia Prática em Produção

01. Gateway de Pagamento para AI SaaS (Stripe / Paddle / LiqPay)

O usuário paga a assinatura do plano Pro. O sistema de pagamento gera o evento invoice.payment_succeeded. O webhook atualiza instantaneamente o status do usuário no banco de dados, reseta os limites de tokens no Redis e envia um e-mail de boas-vindas, permitindo que o usuário continue trabalhando sem recarregar a página.

02. Gatilho de Pipeline Autônomo de CI/CD (GitHub Webhooks)

Um engenheiro executa git push na branch main. O GitHub envia um payload com o hash do commit para o próprio servidor Coolify ou ArgoCD. O servidor verifica o segredo, clona o código alterado, executa testes unitários em um container Docker isolado e atualiza o serviço de produção utilizando a estratégia Blue/Green.

03. Coleta Assíncrona de Resultados de Inferência de Modelos de IA Pesados

Ao chamar modelos de geração de vídeo (Runway, Kling) ou treinamento de LoRA, a requisição pode levar vários minutos. Em vez de manter uma conexão HTTP aberta, o cliente transmite o webhook_url. Assim que o cluster GPU conclui a geração, ele envia um webhook com o link final do S3 para a mídia gerada.


5. Armadilhas, Erros Comuns e Segurança

  1. Parsing de JSON antes da verificação da assinatura: Se o middleware padrão express.json() for executado antes da verificação da assinatura, o serializador pode alterar a ordem dos campos, o escape de caracteres Unicode ou o formato dos números, resultando em incompatibilidade do checksum HMAC e rejeição com erro Invalid Signature.
  2. Bloqueio Síncrono de Execução (Deadlock de Webhook): A execução de transações pesadas ou requisições HTTP longas a APIs externas dentro do handler do webhook garantidamente causará timeout no lado do provedor. Retorne 200 OK imediatamente após a validação da assinatura e o enfileiramento da tarefa.
  3. Ausência de Validação de Timestamps (Ataques de Replay): Um atacante que interceptou uma requisição de webhook legítima e assinada pode reenviá-la uma hora depois. Os provedores incluem um timestamp na assinatura. Verifique se a diferença entre o tempo da requisição e o relógio do sistema do servidor não excede a janela permitida (por exemplo, 5 minutos).
/ Perguntas FrequentesSchema.org FAQPage

FAQ: Webhooks (Assíncronos)

A comparação clássica de strings usando os operadores == ou === é interrompida no primeiro caractere divergente. Um atacante pode medir a latência da resposta do servidor com precisão nanossegundos (Timing Attack) e deduzir byte a byte um hash de assinatura válido. A função timingSafeEqual executa a comparação em tempo fixo, garantindo robustez criptográfica.
/ Links internos
Todos os termos