Garantias de entrega de webhooks
Webhooks - notificações asinhrônicas «sistema para assinante» HTTP (S). A rede não é confiável: as respostas são perdidas, os pacotes vêm duplicados ou fora da ordem. Por isso, as garantias de entrega não são construídas «por TCP», mas no nível de protocolo de webhooks e idempotação de domínio.
O objetivo principal é fornecer uma entrega at-least-once com ordem de chave (onde for necessário), fornecer ao assinante materiais de tratamento idumpotente e uma ferramenta de reconstituição.
1) Níveis de garantia
Best-effort é uma tentativa descartável, sem retais. Aceitável apenas para eventos «irrelevantes».
At-least-once (recomendado) - Pode ser duplicado e out-of-order, mas o evento será entregue desde que o assinante esteja disponível dentro de um prazo razoável.
Effectively-exactly-once (ao nível do efeito) - obtido com uma combinação de idempotação e armazenamento dedup no lado do assinante/remetente. Não é possível usar HTTP «exactly-once» no transporte.
2) Contrato de webhook: mínimo necessário
Cabeçalhos (exemplo):
X-Webhook-Id: 5d1e6a1b-4f7d-4a3d-8b3a-6c2b2f0f3f21 # глобальный ID события
X-Delivery-Attempt: 3 # номер попытки
X-Event-Type: payment.authorized.v1 # тип/версия
X-Event-Time: 2025-10-31T12:34:56Z # ISO8601
X-Partition-Key: psp_tx_987654 # ключ порядка
X-Seq: 418 # монотонный номер по ключу
X-Signature-Alg: HMAC-SHA256
X-Signature: t=1730378096,v1=hex(hmac(secret, t body))
Content-Type: application/json
Corpo (exemplo):
json
{
"id": "5d1e6a1b-4f7d-4a3d-8b3a-6c2b2f0f3f21",
"type": "payment.authorized.v1",
"occurred_at": "2025-10-31T12:34:56Z",
"partition_key": "psp_tx_987654",
"sequence": 418,
"data": {
"payment_id": "psp_tx_987654",
"amount": "10.00",
"currency": "EUR",
"status": "AUTHORIZED"
},
"schema_version": 1
}
A exigência do destinatário é responder rapidamente '2xx' após o tampão e validação da assinatura, e o processamento do negócio é assincrónico.
3) Ordem e causalidade
Ordem da chave: a garantia «não sai» apenas dentro de um 'partition _ key' (por exemplo, 'player _ id', 'wallet _ id', 'psp _ tx _ id').
A ordem global não é garantida.
No lado do remetente, há uma fila com uma seriada por chave (um consumidor/charding), inbox com '(fonte, event _ id)' no lado do destinatário e espera opcionalmente por 'seq' omitidos.
Se as omissões são críticas, forneça pull-API 'GET/events? after = checkpoint 'para o status «perseguir e encurtar».
4) Idempotidade e dedução
Cada webhook leva um 'X-Webhook-Id' estável.
O destinatário armazena 'inbox': PK - 'fonte + event _ id'; repetições → no-op.
Os efeitos secundários (gravação no banco de dados/carteira) são executados apenas uma vez na primeira «visão» do evento.
Para comandos de efeito, use o Idempotency-Key e a caixa de resultados durante a janela de retais.
5) Retraias, backoff e janelas
Política de retrações (arbitragem):- Retrair em '5xx/timeout/connect error/409-Conflict (retryable )/429'.
- Não retraia em '4xx' exceto '409/423/429' (e somente com a semântica acordada).
- Backoff exponencial + full jitter: 0. 5s, 1s, 2s, 4s, 8s, … até 'max = 10-15 min'; As janelas de retrações TTL, por exemplo, 72 horas.
- Respeitar 'Retry-After' no destinatário.
- Compartilhar «reconhecer um evento não entregue» e traduzi-lo para o DLQ.
yaml retry:
initial_ms: 500 multiplier: 2.0 jitter: full max_delay_ms: 900000 ttl: 72h retry_on: [TIMEOUT, 5xx, 429]
6) DLQ и redrive
DLQ - «cemitério» de eventos venenosos ou vencidos por TTL com metainformação completa (paylad, manchetes, erros, tentativas, hachês).
Console Web/API para redrive (reaproveitamento por pontos) com edição opcional de endpoint/segredo.
Rate-limited redrive e batch-redrive com prioridade.
7) Segurança
mTLS (se possível) ou TLS 1. 2+.
Assinatura corporal (HMAC com segredo per tenant/endpoint). Verificação:1. Extrair 't' (timestamp) do cabeçalho, verificar uma janela deslizante (por exemplo, £5 min).
8) Quotas, rate limits e justiça
Fair-Queue per tenant/subscriber: Para garantir que um assinante/tenante não marca um pool comum.
Quotas e limites burst para o tráfego de saída e per-endpoint.
Reação a '429': honrar 'Retry-After', trotar o fluxo; Se a limitação for prolongada, degrade (envio apenas de tipos críticos de eventos).
9) Ciclo de vida da assinatura
Register/Verify: POST endpoint → challenge/response ou confirmação out-of-band.
Lease (opcional): a assinatura é válida até 'valid _ to'; A extensão é clara.
Secret rotation: `current_secret`, `next_secret` с `switch_at`.
Teste ping: evento artificial para verificar a rota antes de incluir os topics principais.
Health-amostras: HEAD/GET periódico com verificação do perfil latency e TLS.
10) Evolução dos circuitos (versões de eventos)
Versionização do tipo de evento: 'payment. authorized. v1` → `…v2`.
Evolução - aditivo (novos campos → MENOR API), breaking → novo tipo.
Registro de esquema (JSON-Schema/Avro/Protobuf) + validação automática antes do envio.
A manchete 'X-Event-Estando' e o campo 'schema _ versão' no corpo são ambos obrigatórios.
11) Observabilidade e SLO
Métricas (tipo/tenante/assinante):- `deliveries_total`, `2xx/4xx/5xx_rate`, `timeout_rate`, `signature_fail_rate`.
- 'attempts _ avg', 'p50/p95/p99 _ delivery _ latency _ ms' (da publicação até 2xx).
- `dedup_rate`, `out_of_order_rate`, `dlq_rate`, `redrive_success_rate`.
- `queue_depth`, `oldest_in_queue_ms`, `throttle_events`.
- Proporção de entregas ≤ 60 c (p95) - 99. 5% para eventos críticos.
- DLQ ≤ 0. 1% em 24 h.
- Signature failures ≤ 0. 05%.
Логи/трейсинг: `event_id`, `partition_key`, `seq`, `attempt`, `endpoint`, `tenant_id`, `schema_version`, `trace_id`.
12) Algoritmo arbitral do remetente
1. Gravar evento em outbox transacionado.
2. Definir partition _ key e seq; colocar na fila.
3. O worker pega na chave, forma o pedido, assina, envia com os temporizadores (connect/read).
4. Em '2xx' - admitir entregue, fixar latência e seq-checkpoint.
5. Com '429/5xx/timeout' - retrai de acordo com a política.
6. Por TTL → DLQ e alert.
13) Processador de arbitragem (destinatário)
1. Aceitar solicitação, verificar TLS/proto.
2. Validação da assinatura e da janela do tempo.
3. ACK rápido 2xx (após gravação sincronizada em inbox/fila local).
4. O worker asinhrônico lê 'inbox', verifique 'event _ id', se necessário - ordena por 'seq' dentro de 'partition _ key'.
5. Executa efeitos, escreve «offset/seq checkpoint» para o recôncil.
6. Em caso de erro, retraias locais; tarefas «venenosas» → DLQ local com alertas.
14) Reconcile (contorno de pula)
Para incidentes «impraticáveis»:- `GET /events? partition _ key =... & after _ seq =... & limit =... '- dar todos os omitidos.
- Token-checkpoint: 'after = opaque _ tocen' em vez de seq.
- Redelivery idimpotente: os mesmos 'event _ id', a mesma assinatura do novo 't'.
15) Títulos e códigos úteis
2xx - aceitou (mesmo que o processamento empresarial seja posterior).
410 Gone - endpoint fechado (o remetente interrompe a entrega e marca a assinatura como «arquivo»).
409/423 - bloqueio temporário do recurso → retrai é inteligente.
429 - muitas vezes → TNT e backoff.
400/401/403/404 - erro de configuração; Parar a retraia, abrir o tíquete.
16) Multi-tenante e regiões
Filas individuais e limites per tenant/endpoint.
Data residency: envio da região de dados; cabeçalho «X-Tenant», «X-Region».
Isolamento de falhas: a queda de um assinante não afeta os outros (separate pools).
17) Testes
Contract testes: exemplos fixos de corpos/assinaturas, verificação de validação.
Chaos: drop/duplicado, shuffle de ordem, atrasos de rede, 'RST', 'TLS'
Load: tempestade burst, paragem p95/p99.
Segurança: anti-réplicas, timestamp obsoletos, segredos errados, rotação.
DR./Replay: redrive em massa de DLQ em um estande isolado.
18) Playbooks (runbooks)
1. Altura de 'marca _ fail _ rate'
Verificar a deriva do relógio que expirou 'tolerance', rotação de segredos; ativar temporariamente «dual secret».
2. Fila envelhecida ('oldest _ in _ queue _ ms' ↑)
Aumentar os workers, priorizar topics críticos, reduzir temporariamente a frequência de tipos ruidosos.
3. Tempestade '429' junto ao assinante
Incluir trottling e pausas entre tentativas; Mover tipos de eventos menos críticos.
4. Massa '5xx'
Abrir o circuito-breaker para um endpoint específico, para o modo defer & batch; sinal ao assinante.
5. Preencher DLQ
Parar a publicação não crítica, incluir batch-redrive com RPS baixo, levantar alertas para os donos de assinaturas.
19) Erros típicos
Processamento pesado sincronizado até a resposta 2xx → retraí e duplicação.
Não há assinatura de corpo/janela do tempo → vulnerabilidade à troca/réplica.
Falta de 'event _ id' e 'inbox' → não é possível tornar a idempotação.
A tentativa de ordem global → os bloqueios das filas para sempre.
Retraias sem jitter/limites → aumento do incidente (thundering herd).
Um único pool comum em todos os seguidores → «barulhento» coloca todos.
20) Folha de cheque antes de vender
- Contrato: 'event _ id', 'partition _ key', 'seq', 'event _ tipo. vN ', assinatura HMAC e timestamp.
- Remetente: outbox, seriado por chave, retraí com backoff + jitter, TTL, DLQ e redrive.
- Destinatário: gravação rápida em inbox + 2xx; tratamento idêntico; DLQ local.
- Segurança: TLS, assinaturas, anti-réplicas, dual-secret, rotação.
- Quotas/limites: fair-queue per tenant/endpoint, respeito 'Retry-After'.
- Recôncil API e checkpoint; documentação para assinantes.
- Observabilidade: p95/fluxos/erros/DLQ, rastreamento por 'event _ id'.
- Versionização de eventos e política de evolução de esquemas.
- Playbooks de incidentes e «botão» de pausa/descongelamento global.
Conclusão
Webhooks confiáveis são um protocolo sobre HTTP, não apenas «POST com JSON». Contrato claro (ID, chave de ordem, assinatura), idempotidade, retraí com jitter, fila justa e playbooks bem resolvidos transformam o «melhor caso» em um mecanismo de entrega previsível e mensurável. Construa at-least-once + ordem à chave + recôncil, e o sistema irá sobreviver com calma à rede, picos de carga e erros humanos.