Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

Webhook Delivery: retries e idempotência no Node.js

Atualizado em: 24 de outubro de 2026

Terminal Linux usado em desenvolvimento e administração de aplicações Node.js

Webhook Delivery no Node.js é o processo de enviar eventos HTTP para sistemas externos com persistência, retries, assinatura, idempotência e rastreabilidade. Uma chamada simples com fetch() pode funcionar em desenvolvimento, mas não oferece recuperação quando o destino está lento, indisponível ou responde de forma ambígua.

O emissor precisa separar a transação de negócio da tentativa de entrega. Primeiro, persiste o evento e a intenção de envio. Depois, um worker busca entregas pendentes, assina o corpo, envia com timeout e registra o resultado.

Modelo de dados

CREATE TABLE webhook_deliveries (
  id uuid PRIMARY KEY,
  endpoint_id uuid NOT NULL,
  event_id uuid NOT NULL,
  event_type text NOT NULL,
  payload jsonb NOT NULL,
  status text NOT NULL,
  attempt_count integer NOT NULL DEFAULT 0,
  next_attempt_at timestamptz,
  last_status integer,
  last_error text,
  created_at timestamptz NOT NULL DEFAULT now(),
  delivered_at timestamptz
);

Adicione índices por status e next_attempt_at. Não guarde respostas sensíveis completas sem necessidade.

Persistindo com Outbox

A intenção de webhook deve ser gravada na mesma transação da mudança de negócio.

await client.query('BEGIN');

await client.query(
  `UPDATE orders SET status = 'paid' WHERE id = $1`,
  [orderId]
);

await client.query(
  `INSERT INTO outbox (id, type, aggregate_id, payload)
   VALUES ($1, 'order.paid.v1', $2, $3::jsonb)`,
  [eventId, orderId, JSON.stringify(event)]
);

await client.query('COMMIT');

Um dispatcher transforma o evento da Outbox em entregas para endpoints inscritos. Veja Outbox Pattern no Node.js.

Selecionando trabalho

const result = await client.query(
  `SELECT *
   FROM webhook_deliveries
   WHERE status IN ('pending', 'retry')
     AND (next_attempt_at IS NULL OR next_attempt_at <= now())
   ORDER BY created_at
   FOR UPDATE SKIP LOCKED
   LIMIT 50`
);

SKIP LOCKED permite vários workers sem processar a mesma linha ao mesmo tempo. Mantenha a transação curta e marque as linhas como em processamento antes da chamada externa.

Payload estável

{
  "id": "evt_123",
  "type": "order.paid.v1",
  "createdAt": "2026-10-24T12:00:00Z",
  "data": {
    "orderId": "ord_45",
    "amount": 12990,
    "currency": "BRL"
  }
}

Inclua ID estável, tipo versionado e timestamp. O consumidor usa o ID para deduplicar.

Assinando a entrega

Calcule HMAC sobre timestamp e bytes exatos do corpo.

const rawBody = Buffer.from(JSON.stringify(payload));
const timestamp = Math.floor(Date.now() / 1000);
const signature = signWebhook({
  secret: endpoint.secret,
  timestamp,
  rawBody
});

Consulte Webhook Signatures no Node.js.

Envio com timeout

const signal = AbortSignal.timeout(10_000);

const response = await fetch(endpoint.url, {
  method: 'POST',
  headers: {
    'content-type': 'application/json',
    'user-agent': 'CodigoFacil-Webhooks/1.0',
    'webhook-id': payload.id,
    'webhook-timestamp': String(timestamp),
    'webhook-signature': signature
  },
  body: rawBody,
  signal
});

Defina limite por conexão, headers e corpo de resposta. Não espere indefinidamente.

Quais respostas indicam sucesso?

Normalmente qualquer 2xx confirma aceitação. Redirecionamentos devem ser tratados com cautela; seguir redirect automaticamente pode enviar segredos e payloads para outro host.

const success = response.status >= 200 && response.status < 300;

Configure redirect: 'manual' e exija que o cliente atualize o endpoint explicitamente.

Erros recuperáveis

Faça retry para:

  • timeout;
  • falha de conexão;
  • 408;
  • 429;
  • 500, 502, 503 e 504.

Respostas como 400, 401, 403 e 404 geralmente exigem correção de configuração, não retry infinito.

Backoff com jitter

function nextDelay(attempt) {
  const base = 5_000;
  const cap = 6 * 60 * 60 * 1000;
  const exponential = Math.min(cap, base * 2 ** attempt);
  return Math.floor(Math.random() * exponential);
}

Respeite Retry-After quando confiável e dentro de limites. Veja Retry com Backoff no Node.js.

Agenda de tentativas

Um exemplo de política:

  • imediata;
  • 30 segundos;
  • 2 minutos;
  • 10 minutos;
  • 1 hora;
  • 6 horas;
  • 24 horas.

Depois do limite, marque como falha permanente e notifique o proprietário do endpoint.

Idempotência do consumidor

Uma resposta pode se perder depois que o destino processou o evento. O emissor fará retry. Por isso, o consumidor deve deduplicar pelo ID do evento.

Consulte Consumer Idempotente no Node.js.

Concorrência por endpoint

Entregar muitos eventos simultaneamente pode derrubar o cliente. Use limite global e limite por endpoint.

const endpointLimiter = new Map();

async function withEndpointLimit(endpointId, task) {
  const limiter = getLimiter(endpointId, 3);
  return limiter.run(task);
}

Preservar ordem exige concorrência 1 por chave ou uma sequência lógica. Nem todos os eventos precisam de ordem total.

Ordenação

Webhooks são naturalmente sujeitos a atraso e retries. Inclua versão do recurso ou timestamp para que o consumidor detecte eventos antigos.

{
  "type": "customer.updated.v2",
  "resourceVersion": 18
}

Para estado atual, o consumidor pode buscar o recurso pela API em vez de confiar que recebeu todos os eventos na ordem.

SSRF

Endpoints configurados por usuários criam risco de Server-Side Request Forgery. Bloqueie IPs privados, loopback, metadata de nuvem e protocolos não HTTP.

  • aceite apenas HTTPS em produção;
  • resolva DNS e valide todos os endereços;
  • revalide após redirect, ou não siga redirects;
  • limite portas;
  • use egress proxy quando possível;
  • proteja contra DNS rebinding.

Desativação automática

Após muitas falhas permanentes, pause o endpoint. Envie alerta e permita reativação após teste de conectividade.

Replay manual

Forneça uma operação administrativa auditada para reenviar uma entrega. O replay deve usar o mesmo ID de evento ou indicar explicitamente que é uma nova tentativa, mantendo deduplicação.

Dead Letter

Entregas esgotadas podem ficar em estado dead. Não as descarte automaticamente; mantenha retenção e ferramentas de diagnóstico.

Veja Dead Letter Queue no Node.js.

Observabilidade

Monitore:

  • taxa de sucesso;
  • latência por endpoint;
  • tentativas por evento;
  • fila pendente;
  • idade do item mais antigo;
  • falhas por status;
  • endpoints pausados;
  • tempo até entrega.

Inclua deliveryId, eventId e endpointId em logs e traces, sem registrar segredo ou payload completo.

Resposta do destino

Leia no máximo alguns kilobytes para diagnóstico. Um servidor malicioso pode enviar resposta infinita.

const text = await readResponseWithLimit(response.body, 8 * 1024);

Testes

  • 2xx;
  • timeout;
  • 429 com Retry-After;
  • 500 seguido de sucesso;
  • 400 permanente;
  • redirect;
  • DNS para IP privado;
  • resposta grande;
  • evento duplicado;
  • dois workers concorrentes;
  • replay manual;
  • rotação de segredo.

Erros comuns

  • enviar dentro da transação de negócio;
  • não persistir tentativas;
  • retry sem limite;
  • seguir redirects automaticamente;
  • não proteger contra SSRF;
  • não incluir ID estável;
  • concorrência ilimitada;
  • esperar ordem perfeita;
  • registrar payload sensível;
  • não oferecer replay.

Checklist

  • Outbox persistida;
  • delivery por endpoint;
  • assinatura e timestamp;
  • timeout;
  • retries com jitter;
  • classificação de status;
  • idempotência;
  • limite por endpoint;
  • proteção SSRF;
  • DLQ e replay;
  • métricas e alertas.

Conclusão

Webhook Delivery no Node.js exige uma fila persistente e política operacional clara. A entrega HTTP é apenas uma tentativa dentro de um ciclo que inclui assinatura, timeout, retry e observabilidade.

Use Outbox, IDs estáveis, backoff e limites de concorrência. Proteja URLs contra SSRF e permita replay auditado. Com esses componentes, webhooks continuam confiáveis mesmo quando sistemas externos falham por horas.

Consulte a documentação do fetch no Node.js e o RFC de semântica HTTP.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita