Ingress Canônico de Webhooks WhatsApp na Borda VPS (Traefik v3)

1. Topologia e Isolamento de Rede

Para eliminar o acoplamento no runtime serverless e mitigar AMQP connection churn, o serviço yzex-worker-service opera como Ingress Gateway de Borda nativo em Node.js (porta 3001), conectado diretamente à rede privada interna yzex-network.

INTERNET
   │ :443 (TLS Let's Encrypt)
   ▼
[Traefik v3 (Container Docker em yzex-network)]
   │ Middleware: body-limit (10MB)
   │ Middleware: rate-limit (100 req/s, burst 50)
   ▼ HTTP Privado (DNS Docker: http://yzex-worker-service:3001)
[yzex-worker-service (Container Docker em yzex-network)]
   │ 1. Header Auth (Constant-Time crypto.timingSafeEqual)
   │ 2. Guardrails (POST, Content-Type, Stream < 10MB)
   │ 3. Fast-Path Deduplication (IN_FLIGHT -> CONFIRMED)
   │ 4. Publisher Confirms (mandatory: true, deliveryMode: 2)
   ▼ AMQP 0-9-1 Persistente (Zero Churn)
[RabbitMQ 4.1.8 Cluster (yzex-rabbitmq:5672)]
   │ Exchange: yzex.events (Topic, Durable)
   ▼ Routing Key: yzex.events.connect.whatsapp.legacy_forward
[Queue: yzex-connect-whatsapp-legacy-forward-queue]

1.1 Invariante de Segurança de Rede

A porta 3001 é restrita exclusivamente à rede interna yzex-network. Nenhuma porta 3001 é aberta no host VPS ou no firewall UFW. O tráfego externo acessa o Ingress unicamente através do Traefik via terminação TLS segura (443).

2. Resoluções Canônicas SRE (P0/P1)

2.1 Desacoplamento de Boot & Healthchecks Semânticos (P0-2)

  • O servidor HTTP sobe de forma 100% síncrona e independente da conectividade com RabbitMQ ou Temporal.

  • /healthz (Liveness Probe): Responde 200 OK imediatamente no boot do Node.js, prevenindo reinícios destrutivos pelo orquestrador enquanto o broker reconecta.

  • /readyz (Readiness Probe): Responde 503 Service Unavailable enquanto a conexão AMQP não estiver pronta. Comuta para 200 OK assim que o canal de publicação com confirmação é estabelecido.

2.2 Máquina de Estados de Deduplicação Fast-Path (P0-3 / P1-3)

  • Memória LRU Local: Retém até 20.000 entradas com TTL de 30 minutos para absorver picos de rajada e retentativas imediatas do provedor.

  • Ciclo de Estados:

    • Requisição inédita: Registrada como IN_FLIGHT.

    • Requisição concorrente com chave idêntica em IN_FLIGHT: Retorna 429 Too Many Requests com header Retry-After: 2.

    • Falha de publicação (Timeout, NACK, basic.return): A chave é imediatamente expurgada da tabela de deduplicação, permitindo que o provedor tente novamente sem falsos positivos.

    • Confirmação do Broker (ACK durável): Comuta para CONFIRMED.

    • Retry posterior com estado CONFIRMED: Retorna 200 OK com {"status": "ALREADY_PROCESSED"}.

  • Segregação Epistêmica: O Ingress atua como Fast-Path Local. A autoridade definitiva de idempotência contra falhas concorrentes de longo prazo é mantida no banco relacional rocketz.webhooks e no consumidor assíncrono.

2.3 Tratamento Rigoroso de Mensagens Não Roteáveis (P0-4)

  • Confirmação formal:

    \$PUBLISHED\_CONFIRMED = publisher\_confirm(ACK) \land \neg basic.return \land \neg timeout\$
  • Um listener dedicado monitora eventos de channel.on('return'). Mensagens devolvidas pelo broker por falta de filas correspondentes são classificadas como UNROUTABLE_MESSAGE, resultando em HTTP 503 e expurgo da chave para reprocessamento.

2.4 Ordem Estrita de Encerramento Gracioso (P0-5)

  1. setIngressReady(false): O endpoint /readyz passa a responder 503, fazendo o Traefik desviar novas conexões.

  2. server.close(): Listener TCP interrompe o aceite de novos sockets.

  3. Dreno de Requisições HTTP Ativas: Aguardo das requisições em voo com teto de 5.000 ms.

  4. Dreno de Confirms AMQP: Confirmação das mensagens pendentes no canal RabbitMQ.

  5. Fechamento de Workers, Canais e Pools de Banco.

2.5 Defesa em Profundidade de Tamanho e Autenticação (P1-1 / P1-2)

  • Limite de Payload: Middleware Traefik body-limit (10 MB) na borda, header Content-Length > 10MB recusado com 413 no Node.js e monitoramento intra-stream abortando a requisição caso ultrapasse 10 MB.

  • Autenticação em Tempo Constante: Prioridade estrita (Authorization: Bearer > X-Webhook-Token > apikey > query fallback). Rejeição com 401 em caso de múltiplos métodos ambíguos. Comparação dos digests SHA-256 via crypto.timingSafeEqual.

3. Contrato Formal da API HTTP

3.1 Endpoints HTTP Homologados

Status Código Semântico Condição / Significado

200 OK

PUBLISHED_CONFIRMED

Mensagem validada, persistida em fila durável com ACK do broker e sem devoluções.

200 OK

ALREADY_PROCESSED

Requisição duplicada com estado prévio CONFIRMED.

400 Bad Request

INVALID_PAYLOAD

JSON malformado ou payload vazio.

401 Unauthorized

UNAUTHORIZED

Credenciais ausentes, inválidas ou ambíguas.

405 Method Not Allowed

METHOD_NOT_ALLOWED

Verbo diferente de POST.

413 Payload Too Large

PAYLOAD_TOO_LARGE

Corpo da requisição excede 10 MB.

415 Unsupported Media Type

UNSUPPORTED_MEDIA_TYPE

Content-Type diferente de application/json ou x-www-form-urlencoded.

429 Too Many Requests

REQUEST_IN_FLIGHT

Evento idêntico em voo sendo processado no momento.

503 Service Unavailable

SERVICE_UNAVAILABLE

Broker desconectado, timeout de confirm (3.000 ms) ou mensagem não roteável (basic.return).

4. Telemetria e SLIs/SLOs de Confiabilidade

O Ingress expõe métricas nativas em /metrics no formato Prometheus:

  • webhook_ingress_requests_total{status}: Totalizadores por desfecho operacional.

  • webhook_ingress_latency_ms{type="http_total_avg"}: Latência total de borda (SLO: p95 < 60 ms).

  • webhook_ingress_latency_ms{type="amqp_confirm_avg"}: Tempo exclusivo de confirmação de escrita em disco do RabbitMQ.

  • webhook_ingress_dedup_hits_total{state="CONFIRMED"}: Taxa de absorção de requisições duplicadas.

  • webhook_ingress_unroutable_total: Alertas de desvio de topologia AMQP.

  • webhook_ingress_inflight_current: Gauge de saturação de conexões em tempo real.