# Entrega e fiabilidade de webhooks: retries, ordenação, dead-letter

> Um webhook é um callback HTTP servidor-a-servidor: a plataforma de mensagens faz POST de um evento ao URL do consumidor. A entrega fiável é at-least-once, pelo que os produtores repetem entregas falhadas com backoff exponencial e jitter, aplicam dead-letter após um limite, e os consumidores têm de ser idempotentes porque duplicados e eventos fora de ordem são esperados.

- URL canônico: https://www.broadnet.me/pt/reference/webhook-delivery-and-reliability
- Idioma: pt
- Última atualização: 2026-07-19
- Editor: BroadNet (https://www.broadnet.me)

A arquitetura padrão de webhook/callback e o seu modelo de fiabilidade: entrega at-least-once, backoff exponencial com jitter nos retries, timeouts de entrega, garantias de ordenação, filas dead-letter, consumidores idempotentes e saúde do endpoint / circuit-breaking para proteger um recetor em falha.

## Arquitetura de webhook

Um webhook inverte os papéis habituais de cliente/servidor: em vez de a aplicação fazer polling à plataforma de mensagens à procura de atualizações, a plataforma faz POST de um evento a um URL que a aplicação registou — uma mudança de estado de entrega, uma mensagem de entrada, um evento de conta. Esta é a metade push do modelo assíncrono cuja metade pull é o polling a GET /messages/{id}. O endpoint do consumidor é um servidor HTTPS comum que tem de aceitar um POST, fazer o mínimo necessário para registar o evento de forma durável e devolver um 2xx rapidamente. Um padrão comum e importante é reconhecer na receção e processar de forma assíncrona: o endpoint escreve o evento em bruto numa fila ou log, devolve 200, e um worker separado faz o trabalho real — para que uma lógica de negócio lenta nunca faça o produtor entrar em timeout e repetir.

*Um POST de webhook de estado de entrega para o consumidor*

```http
POST /webhooks/dlr HTTP/1.1
Host: app.example
Content-Type: application/json
Webhook-Id: 8b1a...
Webhook-Timestamp: 1752916522
Webhook-Signature: v1,3f2b...

{
  "event": "message.delivered",
  "message_id": "01HZY8Q3M6",
  "status": "delivered",
  "occurred_at": "2026-07-19T09:15:25Z"
}
```

## Garantias de entrega: at-least-once

A entrega distribuída não pode ser simultaneamente exactly-once e disponível, pelo que quase todos os sistemas de webhook em produção escolhem at-least-once: o produtor continua a repetir até ver um reconhecimento de sucesso, aceitando que um consumidor possa ocasionalmente receber o mesmo evento mais do que uma vez. Os extremos alternativos são piores — at-most-once descarta eventos na primeira falha, e o verdadeiro exactly-once exige uma coordenação distribuída que o transporte HTTP não fornece. At-least-once impõe uma obrigação ao consumidor: tem de ser idempotente, desduplicando por um identificador de evento estável para que um evento reentregue seja inócuo. O contrato de reconhecimento é simples: qualquer 2xx significa 'parar de repetir'; tudo o resto (ou um timeout) significa 'repetir mais tarde'.

## Retries com backoff exponencial e jitter

Quando uma entrega falha ou entra em timeout, o produtor repete segundo um cronograma que cresce exponencialmente — um atraso base duplicado a cada tentativa até um teto — para que um endpoint momentaneamente indisponível não seja martelado. Fundamental: o atraso inclui jitter aleatório; sem ele, uma frota de eventos que falharam todos durante a mesma indisponibilidade repetiria em sincronia e produziria uma thundering herd sincronizada no instante em que o endpoint recuperasse. O full jitter (um valor aleatório entre zero e o limite de backoff atual) distribui essa carga de forma suave. Os retries continuam até o evento ter sucesso ou ser atingido um limite total de tentativas ou de tempo, após o qual o evento vai para dead-letter em vez de ser repetido para sempre.

*Backoff exponencial com full jitter*

```text
base = 2s ; factor = 2 ; cap = 3600s
attempt n bound = min(cap, base * factor^(n-1))
delay(n)        = random(0, bound)      # full jitter

n=1  bound  2s    delay in [0, 2s]
n=2  bound  4s    delay in [0, 4s]
n=3  bound  8s    delay in [0, 8s]
n=4  bound 16s    delay in [0, 16s]
...  until success or attempt/time cap -> dead-letter
```

## Timeouts e ordenação

Um produtor define um timeout curto por entrega — normalmente alguns segundos — e trata um timeout do mesmo modo que uma falha: a ligação é abandonada e o evento é agendado para retry. Esta é a razão mecânica pela qual os consumidores devem reconhecer depressa e processar de forma assíncrona. A ordenação é a garantia mais difícil. Como os retries e a entrega paralela se entrelaçam, um fluxo de webhook ingénuo tem ordenação apenas best-effort: um evento anterior repetido pode chegar depois de um posterior. Os sistemas que precisam de ordem ou entregam por chave em sequência (todos os eventos de um message id numa única partição ordenada, bloqueando em caso de falha) ou, mais comummente, tornam o payload auto-ordenável — cada evento traz um timestamp e uma sequência monótona para que o consumidor descarte uma atualização obsoleta em vez de depender da ordem de chegada.

*Estratégias de ordenação para fluxos de webhook*

| Estratégia | Como | Custo |
|---|---|---|
| Best-effort | Entregar conforme disponível, tentar novamente de forma independente | Os eventos podem chegar fora de ordem |
| Ordenado por chave | Serializa todos os eventos para um ID; bloqueia em caso de falha | Bloqueio head-of-line, menor throughput |
| Payload auto-ordenado | Transporta timestamp + sequência; o consumer descarta os obsoletos | Lógica do consumer, mas sem bloqueio |

## Dead-letter e saúde do endpoint

Um endpoint que permanece em baixo não deve consumir capacidade de retry indefinidamente. Depois de esgotada a política de retry, o evento é movido para uma fila dead-letter — um armazenamento durável de eventos não entregáveis que um operador pode inspecionar e reprocessar assim que o endpoint estiver corrigido — em vez de ser descartado em silêncio. Para proteger ambos os lados enquanto um endpoint está em mau estado, os produtores acompanham a saúde de entrega por endpoint e aplicam circuit-breaking: após uma sequência sustentada de falhas o circuito abre e os novos eventos são estacionados ou rejeitados de imediato em vez de tentados, e depois uma sondagem half-open testa periodicamente a recuperação antes de a entrega normal recomeçar. Isto impede que um consumidor morto gere um backlog de tentativas condenadas e permite que um em recuperação acelere de forma graciosa. O comportamento concreto de webhook e retry da BroadNet está documentado para programadores.

## Construir um consumidor idempotente

Como a entrega at-least-once garante duplicados, é o consumidor — e não o produtor — que assume os efeitos exactly-once. O padrão é um armazenamento de desduplicação indexado por um identificador de evento estável, fornecido pelo produtor (um Webhook-Id ou event id, nunca só o hash do payload). Na receção, o consumidor verifica se já processou esse id; se sim, reconhece com 2xx e nada mais faz; se não, regista o id e aplica o efeito na mesma transação, para que uma falha não possa aplicar o efeito sem registar o id. Combinado com payloads auto-ordenáveis, isto torna um evento reentregue ou fora de ordem um no-op, que é o que transforma um transporte at-least-once em resultados de negócio exactly-once.

*Consumidor idempotente (pseudocódigo)*

```text
on POST(event):
  verify_signature(event)          # reject if invalid
  id = event.headers['Webhook-Id']
  begin transaction:
    if seen(id): return 200        # duplicate -> no-op
    if event.sequence <= last_seq(event.message_id):
        mark_seen(id); return 200  # stale -> discard
    apply_effect(event)
    mark_seen(id); set_last_seq(...)
  commit
  return 200
```

## Perguntas frequentes

### Que garantia de entrega os webhooks oferecem?

At-least-once. O produtor repete até ver um reconhecimento 2xx, pelo que um consumidor pode receber o mesmo evento mais do que uma vez. O exactly-once sobre HTTP puro exigiria uma coordenação distribuída que o transporte não fornece, pelo que se espera que o consumidor seja idempotente e desduplique por um event id estável.

### Porque adicionar jitter ao backoff de retry dos webhooks?

Sem jitter, todos os eventos que falharam durante uma indisponibilidade repetem em sincronia quando o endpoint recupera, criando uma thundering herd sincronizada que o pode voltar a derrubar. O full jitter — um atraso aleatório entre zero e o limite de backoff atual — distribui os retries de forma suave para que o endpoint em recuperação veja uma subida gradual.

### Como deve um endpoint de webhook responder?

Depressa e com um 2xx. O padrão recomendado é registar de forma durável o evento em bruto, devolver 200 de imediato e processar de forma assíncrona num worker separado. Assim, uma lógica de negócio lenta nunca faz disparar o timeout de entrega do produtor e desencadear um retry desnecessário.

### Os eventos de webhook são entregues por ordem?

Por omissão, apenas best-effort — os retries e a entrega paralela podem entrelaçar-se, pelo que um evento anterior pode chegar depois de um posterior. Para obter ordem, ou se serializa por chave (ao custo de head-of-line blocking) ou se torna o payload auto-ordenável com um timestamp e um número de sequência para que o consumidor descarte atualizações obsoletas.

### O que é uma fila dead-letter para webhooks?

Um armazenamento durável para eventos que não puderam ser entregues depois de esgotada a política de retry. Em vez de os descartar em silêncio, o produtor estaciona-os para que um operador possa inspecionar as falhas e reprocessar os eventos assim que o endpoint do consumidor estiver corrigido.

### Porque aplicar circuit-breaking aos endpoints de webhook?

Um endpoint persistentemente em falha consumiria capacidade de retry com tentativas condenadas e acumularia um backlog. O circuit-breaking abre após falhas sustentadas, estacionando ou rejeitando de imediato os novos eventos, e usa depois uma sondagem half-open para testar a recuperação antes de retomar a entrega normal — protegendo tanto o produtor como o consumidor em recuperação.

## Fontes

- [IETF RFC 9110 — HTTP Semantics (method properties, 202)](https://datatracker.ietf.org/doc/html/rfc9110)
- [IETF draft — Idempotency-Key HTTP Header Field](https://datatracker.ietf.org/doc/html/draft-ietf-httpapi-idempotency-key-header)

## Relacionados

- [Verificação de assinatura de webhook](https://www.broadnet.me/pt/reference/webhook-signature-verification)
- [Estado de mensagem e webhooks de entrada](https://www.broadnet.me/pt/reference/message-status-and-inbound-webhooks)
- [Idempotência e rate limiting](https://www.broadnet.me/pt/reference/idempotency-and-rate-limiting)
- [Guia de webhooks da BroadNet](https://www.broadnet.me/pt/developers/webhooks)
- [Retries e backoff da BroadNet](https://www.broadnet.me/developers/retries)
