0023 — Apagão de webhook: watchdog comercial e resgate da 1ª captura já paga¶
Status: Aprovado · Responsável: Gustavo Madruga · Atualizado em: 2026-09-21 · Decidido em: 2026-09-21
Contexto¶
Em 18/09/2026 (sexta) quatro pedidos pagos de manhã não chegaram ao X-Adm: 260082900,
260083184, 260083199, 260083202. O cliente percebeu na segunda e importou um à mão.
A reconstrução no banco e no log do container fecha a cadeia:
| hora (BRT) | evento | fonte |
|---|---|---|
| 07:59:26 | último webhook recebido | log WebhookController |
| 08:02:58 | Falha de rede/timeout (1/3): Read Timeout |
log IntegradorClient |
| 08:03:03 | Connect Error: int.maxsul.xadm.biz: Temporary failure in name resolution |
log |
| 08:08:13 | última falha de rede — a degradação durou ~5 min | log |
| 09:12 – 10:37 | os quatro pedidos viram order (orderCreated) e são pagos |
pied_rest |
| 10:43:44 | webhook volta — 2h44 de silêncio | log |
| 11:01:49 | poll REST de 6h captura os quatro — já received |
pied_rest |
A rede do servidor oscilou por cinco minutos; a PIED tomou timeout ao entregar e desativou o webhook. O apagão durou 2h44 — 33× a falha de rede — e só terminou quando alguém recadastrou o webhook no dashboard da PIED com a mesma URL. É o modo de falha da decisão 0016, disparado por timeout e não por 404.
O app não teve culpa nem participação: container StartedAt=2026-09-17T19:46:36Z, restarts=0, e o
NormalizadorService logando de cinco em cinco minutos durante todo o silêncio. Estava ouvindo; não
chegou nada.
Dois pontos cegos deixaram o estrago passar:
- O watchdog não viu. Ele media dia útil inteiro (24h úteis, seg–sex). Um apagão de 2h44 dentro do expediente nem se aproxima do limite — e é exatamente a janela em que o prejuízo acontece, porque é quando pedidos nascem.
- O backstop resgatou o dado e não conseguiu entregá-lo. O poll REST existe para cobrir buraco de
webhook e fez o seu papel: trouxe os quatro. Mas o gate de envio é a transição de pagamento
para
received, e quem só vê o pedido depois de pago não tem transição a testemunhar. Os quatro encalharam emCAPTURADO, invisíveis. O fallback e o gate se anulavam justamente no caso que o fallback existia para cobrir.
O ponto 2 já estava registrado como trade-off conhecido ("o furo da 1ª captura", pendencias.md P1) com o console como válvula manual. O apagão mostrou que a válvula manual não basta: ninguém olha o console sem saber que há o que olhar, e o ponto 1 garantia que ninguém soubesse.
Decisão¶
Fechar os dois pontos cegos. As duas metades são complementares — a primeira avisa, a segunda resgata sozinha — e nenhuma substitui a outra.
A — Watchdog com régua comercial¶
O WatchdogJob passa a alertar por duas réguas, a que vier primeiro:
- comercial — minutos de silêncio dentro do expediente da Maxsul (seg–sex, 8h–18h, informado
pelo cliente), limite 45 min (
PIED_WATCHDOG_LIMITE_MIN_COMERCIAL); - dia útil — as 24h úteis de hoje (
PIED_WATCHDOG_LIMITE_HORAS), mantida como rede do apagão que começa fora do horário e atravessa a noite ou o fim de semana.
O intervalo do job cai de 1h para 5min: varrer de hora em hora anularia uma régua medida em dezenas de minutos. O alerta segue 1x por episódio e as duas réguas dividem o mesmo episódio — a que estourar primeiro alerta, a outra se cala.
Os 45 min são medidos, não chutados. Em 6.935 intervalos entre webhooks dentro do comercial (set/2026), apenas dois passaram de 15 min: o apagão de 26h de 14–15/09 (falha real, que a régua antiga pegou) e um gap de 37 min no almoço de 16/09. Nenhum outro acima de 20 min. 45 não gera falso positivo e teria alertado o apagão de 18/09 por volta das 08:45 — antes de o primeiro dos quatro pedidos existir (09:12).
A mensagem do alerta passa a citar o timeout ao lado do 404 como causa provável, e a dizer que a PIED não reenvia o que se perdeu — o recadastro restabelece o fluxo, não recupera o buraco.
B — Resgate da 1ª captura já paga¶
Quando o pedido é visto pago já na primeira captura, o upsert passa a poder enfileirá-lo direto
(NA_FILA), sem transição testemunhada. O discriminador é o orderCreated da PIED, que o REST
traz (o webhook não): apagão produz pedido de horas atrás; backlog antigo, de meses.
A regra vive em GatePrimeiraCaptura (pura, testável) e é levada ao SQL como um booleano — o upsert
continua sendo quem sabe se houve transição. Vale tanto no INSERT quanto no DO UPDATE de um
CAPTURADO que nunca transicionou (pago_em IS NULL), porque o pedido pode ter entrado antes pelo
webhook (sem orderCreated) e só ganhar o campo numa passagem posterior do REST.
Duas cercas impedem despejo de backlog:
- piso (
PIED_GATE_PRIMEIRA_CAPTURA_DESDE,AAAA-MM-DD): só pedido criado depois da data de ativação. Vazio desliga o resgate, e vazio é o default — ligar é decisão de deploy. Sem o piso, subir esta versão enviaria na primeira rodada quatro pedidos legados (260081247,260081395,260081535,260082852) que já andaram no fluxo e podem ter sido digitados no ZIM à mão; como o X-Adm só fazINSERT, isso viraria pedido duplicado. - janela (
PIED_GATE_PRIMEIRA_CAPTURA_JANELA_DIAS, default 7): idade máxima doorderCreated. Cobre o apagão que atravessa um fim de semana e nunca alcança o backlog da PIED (21 mil pedidosCAPTURADO, alguns de 2025).
O Enfileirar manual do console continua existindo para o que ficar fora das cercas.
Consequências¶
- O apagão vira visível em menos de uma hora, dentro do expediente, com a ação no próprio alerta. Fora do expediente nada muda — ninguém agiria antes das 8h de qualquer forma.
- O backstop volta a ser um backstop de verdade: o poll REST que resgata o pedido perdido agora consegue entregá-lo. O par apagão-curto + poll-de-6h deixa de produzir pedido encalhado.
- O deploy é inerte por default. Sem
PIED_GATE_PRIMEIRA_CAPTURA_DESDE, B não muda nada; o comportamento é idêntico ao de antes. Ligar exige a data explícita. - O gate segue sendo a transição no caso normal. B não relaxa o gate: é uma exceção cercada para quem nunca teve transição a ser vista.
- Pedido sem
orderCreatedno payload nunca é resgatado (falha fechada) — cai no console, como hoje. - O watchdog roda 12× mais vezes; o custo é uma consulta de
max(recebido_em)por rodada, sob o mesmo lock de cluster (0014).
Alternativas consideradas¶
- Limite comercial de 30 min: rejeitado — o gap de 37 min do almoço de 16/09 viraria alerta, e um watchdog que cria ruído semanal é um watchdog que o operador aprende a ignorar. 45 dá margem e ainda pega o apagão bem antes do primeiro pedido.
- Trocar a régua de 24h úteis pela comercial: rejeitado — apagão iniciado às 18:01 de uma sexta ficaria sem nenhuma régua até segunda de manhã. Duas redes, custo quase zero.
- B sem piso, só com a janela de 7 dias: rejeitado — enviaria os quatro legados no primeiro ciclo, com risco de duplicata no ZIM que ninguém pediu para correr. O piso torna o deploy inerte.
- Janela de 2 dias em vez de 7: rejeitado — hoje pegaria zero legados, mas não cobre o apagão que começa sexta à tarde e só é notado na segunda, que é a forma mais provável do próximo incidente.
- Enfileirar todo
CAPTURADO+received: rejeitado pelo mesmo motivo de sempre (decisão de 2026-08-07) — despeja backlog antigo da PIED no ERP. - Detectar o apagão pelo lado da PIED (consultar se o webhook está ativo): fora de alcance, não há endpoint documentado; o cadastro é manual no dashboard (runbook).