Webhooks: repetições e recibos
1) Modelo básico de entrega
At-least-once (padrão): o evento será entregue ≥1 vezes. As garantias são uma ou outra vez alcançadas pela idimpotência do receptor.
Recibo (ACK): Somente qualquer 2xx (normalmente 200/204) do destinatário significa sucesso. Tudo o resto é interpretado como uma rejeição e leva a uma repetição.
ACK rápido: responda 2xx após a inserção do evento por sua vez, e não após o processamento completo do negócio.
2) Formato de evento e títulos obrigatórios
Carga útil (exemplo)
json
{
"id": "evt_01HXYZ",
"type": "order. created",
"occurred_at": "2025-11-03T18:10:12Z",
"sequence": 128374,
"source": "orders",
"data": { "order_id": "o_123", "amount": "49. 90", "currency": "EUR" },
"schema_version": 1
}
Cabeçalhos do remetente
'X-Webhook-Id: evt _ 01XYZ' é um ID de evento exclusivo (use para deduplicar).
'X-Webhook-Seq: 128374' é uma sequência monótona (por assinatura/tema).
`X-Signature: sha256=<base64(hmac_sha256(body, secret))>` — HMAC-подпись.
'X-Retry: 0,1,2...' é o número da tentativa.
'X-Webhook-Versão: 1' - Versão do contrato.
(opcional) 'Traceparent' - correlação de trilhos.
Resposta do destinatário
2xx - aceito com êxito (não haverá mais repetições sobre este 'id').
410 Gone - endpoint removido/inativo → o remetente interrompe as repetições e desativará a assinatura.
429/5xx/timeout - o remetente repete a política de retrações.
3) Política de repetição (retries)
Escada recomendada backoff (+ jitter)
'1s, 3s, 10s, 30s, 2m, 10m, 30m, 2h, 6h, 24h' (paramos após o limite, por exemplo, 48-72 horas).
Regras:- Backoff exponencial + jitter aleatório (£20-30%) para evitar «efeito rebanho».
- Quórum de erro para falhas temporárias (por exemplo, repetição, se 5xx ou tempo de rede).
- Respect 429: coloque o mínimo 'min' (título Retry-After, próxima janela backoff) '.
Temporizações e tamanhos
Tempo de conexão ≤ 3-5 segundos; tempo total de resposta ≤ 10 segundos.
Tamanho corporal por contrato (por exemplo, ≤ 256 KB), senão 413 → a lógica «chunking» ou «pull URL».
4) Idempotidade e dedução
Aplicação Idumpotente: O processamento de repetições do mesmo 'id' deve devolver o mesmo resultado e não alterar novamente o status.
Armazenamento do lado do destinatário: armazenar '(X-Webhook-Id, processed _ at, checksum)' s TTL ≥ retais (24-72 h).
Chave composta: se vários topics → '(subscrição _ id, event _ id)'.
5) Ordem e efeitos «exactly-once»
Garantir uma ordem rigorosa é difícil em sistemas distribuídos. Use:- Partition by key: O mesmo conjunto lógico (por exemplo, 'order _ id') está sempre no mesmo canal de entrega.
- Sequence: Rejeite os eventos com o antigo 'X-Webhook-Seq' e coloque-os em 'check lot' antes que os faltosos cheguem.
- registro de operações aplicadas (outbox/inbox pattern),
- upsert transacionado por 'event _ id' no banco de dados,
- sagas/compensações para processos complexos.
6) Solução de erros de status de código (tabela)
7) Segurança do canal
A assinatura HMAC de cada mensagem; verificação em um receptor com «janela de tempo» (mitm e ataques replay).
mTLS para domínios sensíveis (CUS/pagamentos).
IP allowlist endereços de saída, TLS 1. 2+, HSTS.
PII Minimização: Não envie mais dados pessoais; disfarce nos logs.
Rotação de segredos: duas chaves válidas (ative/next) e o cabeçalho 'X-Key-Id' para especificar a chave atual.
8) Filas, DLQ e réplicas
Os eventos são necessariamente escritos na fila de saída/registro no lado do remetente (para uma réplica confiável).
Se ultrapassar o máximo de retrações, o evento vai para o DLQ (Dead Letter Queue) com a causa.
API replay (para destinatário/operador): reenviação por 'id '/intervalo de tempo/tema, com RPS restrito e assinatura/autorizada adicional.
POST /v1/webhooks/replay
{ "subscription_id": "sub_123", "from": "2025-11-03T00:00:00Z", "to": "2025-11-03T12:00:00Z" }
→ 202 Accepted
9) Contrato e versão
Versionize o evento (campo 'schema _ versão') e o transporte ('X-Webhook-Versão').
Adicione os campos apenas como opcionais; quando removida, migração menor e transição (dual-write).
Documente os tipos de evento, exemplos, esquemas (JSON Schema), códigos de erro.
10) Observabilidade e SLO
Métricas-chave do remetente:- 'delivery _ sucess _ rate' (2xx/todas as tentativas), 'first _ attempt _ sucess _ rate'
- `retries_total`, `max_retry_age_seconds`, `dlq_count`
- `latency_p50/p95` (occurred_at → ack_received_at)
- `ack_latency` (receive → 2xx), `processing_latency` (enqueue → done)
- `duplicates_total`, `invalid_signature_total`, `out_of_order_total`
99. 9% dos eventos recebem o primeiro ACK ≤ 60 segundos (28d).
- DLQ ≤ 0. 1% do total; réplicas DLQ ≤ 24 h.
11) Timing e quebras de rede
Use UTC nos campos de tempo; sincronize o NTP.
Envie 'occurred _ at' e anote 'delivered _ at' para contar a liga.
Em caso de interrupções prolongadas de rede/endpoint → acumule na fila e limite o crescimento (backpressure + quotas).
12) Limites recomendados e higiene
RPS para assinatura (por exemplo, 50 RPS, burst 100) + paralelismo (por exemplo, 10).
Max. corpo: 64-256 KB; para mais - «notificação + URL» e assinatura para download.
Nomes de evento em 'snake. case 'ou' dot. type` (`order. created`).
Idempotidade rigorosa das operações write do receptor.
13) Exemplos: remetente e destinatário
13. 1 Remetente (pseudocode)
python def send_event(event, attempt=0):
body = json. dumps(event)
sig = hmac_sha256_base64(body, secret)
headers = {
"X-Webhook-Id": event["id"],
"X-Webhook-Seq": str(event["sequence"]),
"X-Retry": str(attempt),
"X-Signature": f"sha256={sig}",
"Content-Type": "application/json"
}
res = http. post(endpoint, body, headers, timeout=10)
if 200 <= res. status < 300:
mark_delivered(event["id"])
elif res. status == 410:
deactivate_subscription()
else:
schedule_retry(event, attempt+1) # backoff + jitter, respect 429 Retry-After
13. 2 Destinatário (pseudocode)
python
@app. post("/webhooks")
def handle():
body = request. data headers = request. headers assert verify_hmac(body, headers["X-Signature"], secret)
evt_id = headers["X-Webhook-Id"]
if dedup_store. exists(evt_id):
return, "" 204 enqueue_for_processing (body) # fast path. dedup_store put(evt_id, ttl=723600)
return, "" 202 # or 204
14) Testes e práticas de caos
Maletas negativas: assinatura de nevalida, 429/5xx, tempo, 410, grande payload's.
Comportamentos: out-of-order, duplicados, atrasos de 1 a 10 minutos, separação de 24 horas.
Cargas: burst 10 x; verifique o backpressure e a estabilidade do DLQ.
Contratos: JSON Schema, títulos obrigatórios, eventos estáveis.
15) Folha de cheque de implementação
- 2xx = ACK, e retorno rápido após enqueê
- Backoff exponencial + jitter, respeito 'Retry-After'
- Idempotidade do receptor e dedução por 'X-Webhook-Id' (TTL ≥ retraí)
- Assinaturas HMAC, rotação de segredos, optional mTLS
- DLQ + Replay API, monitoramento e alertas
- Limitações: timeout, RPS, tamanho do corpo
- Ordem: partition by key ou 'sequence' + 'parcelamento lot'
- Documentação: esquemas, exemplos, codificações de erro, versões
- Testes de caos: atrasos, duplicações, falhas de rede, replay prolongado
16) Mini-FAQ
Devemos sempre responder 200?
Qualquer 2xx é considerado um sucesso. 202/204 - Prática normal para «aceito na fila».
Podemos parar as repetições?
Sim, a resposta 410 e/ou através do console/API do remetente (desativar a assinatura).
Como estar com os grandes payload 'ami?
Envie «notificação + secure URL», assine o pedido de download e instale o TTL.
Como garantir a ordem?
Partition by key + `sequence`; Quando a discrepância é «estacionamento lot» e reaproximação.
Resultado
Webhooks confiáveis são uma semântica ACK clara (2xx), repetições razoáveis com backoff + jitter, idempotação rigorosa e dedução, segurança adequada (HMAC/mTLS), filas + DLQ + réplicas, e observabilidade transparente. Fixe o contrato, insira limites e métricas, execute os cenários de caos regularmente e as suas integrações deixam de ser erodidas na primeira falha.