Webhook, eventos e reconciliação na Nero¶
Para: Dev — Conferido em 2026-10-18.
Três rotinas mantêm o livro da Nero igual ao do Dracma: a rota do webhook, o job de GET /events e a reconciliação diária. Todas terminam no mesmo aplicador de estado.
O que é¶
O aplicador é o ponto único da regra de dinheiro: dedup, fetch-back, coerência com o intent, versão e efeito idempotente. O desenho geral está em Arquitetura do conector.
Quando usar¶
Ao implementar o receptor e os jobs do nero_payments, e ao revisar o alerta de cada um.
Como funciona¶
Rota do webhook¶
POST /api/payments/dracma/webhook passa pelo portal sem autenticação de jogador. A defesa é:
- Assinatura verificada no
nero_paymentssobre o corpo cru (HMAC-SHA256 com o segredo de webhook, que é diferente do segredo de chamada). - Fetch-back antes de mover dinheiro: um segredo de webhook vazado sozinho não cria dinheiro.
- Caddy da bet: o Dracma não publica lista de IPs de saída hoje; se um dia vier por contrato, aplique-a só nessa rota. É uma camada extra, não substitui as duas anteriores.
A ordem de verificação (13 passos) e a tabela "você responde X, o Dracma faz Y" estão em Receptor passo a passo. Na Nero:
- Responda em até 10 segundos. Se o trabalho for pesado, grave o evento de forma durável e responda
200; um worker processa. - Nunca siga redirect nem responda
3xx: o Dracma trata como falha. - Tipo de evento ou estado desconhecido:
200, grava para dedup, não move dinheiro. - Métrica
nero_dracma_webhook_verify_total{result}com os resultadosok,bad_signature,stale,mismatch_event_ideduplicate. Alerta embad_signaturemaior que 0, que pode ser ataque ou rotação mal feita.
Os clientes Go e Node de Exemplos executáveis já implementam a verificação de assinatura e passam nos vetores.
Job de GET /events a cada 5 minutos¶
a cada 5 min (um único executor ativo; lock no banco):
cursor = ler_cursor("dracma_events") # último evt APLICADO; nulo no primeiro run
repetir até 20 páginas:
r = GET /v1/events?after={cursor}&limit=200 # sem after no primeiro run
se r.status == 429: esperar Retry-After; continuar
se r.status >= 500 ou erro de rede: parar (tenta no próximo ciclo)
para ev em r.data:
aplicar_evento(ev) # MESMO aplicador do webhook, a partir do dedup
cursor = ev.id; salvar_cursor(cursor) # só depois de aplicar
se r.next_cursor == nulo: parar
aftertem de ser um evento da operação da Nero; id desconhecido dá400comreason: unknown. Guarde o cursor no banco, nunca em memória.GET /eventsestá no balde de listagem (5 req/s por chave): 20 páginas de 200 eventos por ciclo é folgado.- Os eventos de
/eventsnão têm assinatura própria: a autenticidade vem da chamada autenticada da Nero. - Métrica
nero_dracma_events_lag_seconds(agora menos ocreated_atdo último aplicado). Alerta se o job não roda com sucesso há mais de 15 minutos. - O poller pronto, com cursor, repetição e teste, está em Exemplos executáveis.
Reconciliação diária¶
todo dia às 02:30 UTC, para D = ontem (UTC):
itens = []; cursor = nulo
repetir:
r = GET /v1/reconciliation?date=D¤cy=BRL&limit=200[&cursor=...]
se r.status == 409 (day_open): reagendar para depois do Retry-After; parar
itens += r.items; cursor = r.next_cursor
até cursor == nulo
exigir sha256(linhas ordenadas por (object_id, kind)) == r.digest # senão incidente
exigir Dracma-Signature da última página válida com o segredo de webhook # senão incidente
comparar com o nero_ledger:
todo deposit paid tem exatamente 1 crédito com o mesmo external_id e valor
todo crédito da Nero tem 1 deposit paid # crédito sem par é o mais grave
todo withdrawal paid corresponde a uma reserva finalizada, e vice-versa
guardar o relatório e o header de assinatura como prova
- Divergência é incidente: não "conserte" o livro para bater. Congele o que for preciso e investigue pelo
trace_ide porGET /v1/events?object_id=. - Compare pelo
settled_at(UTC) do Dracma, não pela data local de Brasília: itens perto da meia-noite UTC caem no dia seguinte do calendário local. DigesteVerifyDigestjá estão nos exemplos Go e Node.- Métrica
nero_dracma_reconciliation_mismatch_total; alerta crítico em qualquer valor maior que 0.
Alertas mínimos da Nero¶
Os alertas genéricos e a razão de cada um estão em Alertas do conector. Os limiares da Nero:
| Sinal | Limiar | Severidade |
|---|---|---|
nero_dracma_outbox_pending com intent mais velho que |
10 minutos | P2 |
| Webhook com assinatura inválida | Mais de 0 em 5 minutos | P2 |
401 ou 403 nas chamadas da Nero ao Dracma |
Mais de 0 | P2 (credencial, relógio ou operação suspensa) |
Desvio de relógio (server_time de GET /v1/ping) |
Mais de 5 segundos | P2 |
Job de /events sem sucesso |
Mais de 15 minutos | P2 |
Fila needs_decision com caso parado |
Mais de 4 horas aviso, mais de 24 horas crítico (D11) | P2, depois P1 |
nero_dracma_fetchback_mismatch_total |
Mais de 0 | P1 |
| Reconciliação diária com divergência | Mais de 0 | P1 |
Exemplos¶
O poller e o parser de erro prontos estão em Exemplos executáveis. Para provar a rota do webhook, use a Receita 10 (reentrega) e a Receita 11 (webhook forjado).
Erros e casos de borda¶
- O job e o webhook processam o mesmo evento: o dedup por id do evento é o que impede o crédito duplo.
- Evento que chega fora de ordem: aplique só a versão maior que a guardada no intent.
- Fetch-back responde
404: responda200sem efeito. Rede ou5xx: responda503, e o Dracma reentrega.