Event Processing Affidabile (B2B SaaS)

2026Architettura + Backend Engineering

Un layer affidabile per webhook e integrazioni SaaS: processa eventi esterni in sicurezza, evita duplicazioni e mantiene osservabilità operativa.

Node.jsPostgreSQLNestJSQueuesObservability

Cos'è

Un servizio piccolo ma production-ready che riceve webhook, persiste gli eventi, li processa in asincrono e applica gli effetti una sola volta — anche quando il provider ritenta, duplica o invia payload errati.

Contesto

Eventi esterni (webhook, pagamenti, integrazioni) sono input non affidabili. Processarli direttamente trasforma retry e duplicati in stato inconsistente e rischio finanziario.

Failure modes

I provider ritentano in modo aggressivo, i payload arrivano malformati, l'ordine di consegna non è garantito. Il sistema deve restare corretto anche con input rumoroso.

Cosa garantisce

Comportamento prevedibile con retry, duplicati e input malformato. Visibilità completa per operare il sistema. Quando qualcosa fallisce: vedi il motivo, decidi l'azione, mantieni lo stato consistente.

Garanzie chiave

Niente duplicazioni

Retry e duplicati non causano doppie attivazioni o doppie operazioni.

Chiavi di idempotenza + ledger degli effetti.

Fail sicuro, zero stato parziale

Eventi malformati falliscono in modo sicuro e il motivo resta registrato.

Validazione deterministica + reason persistita.

Controllo operativo

I retry sono manuali e tracciati: l'operatore resta in controllo.

Requeue manuale con actor/reason/timestamp.

Dettagli architetturali

Flusso operativo

Ingest

Valida e persiste l'evento. Risponde 202 immediatamente.

Ledger + Job

Aggiunge l'evento al ledger immutabile e crea un job.

Worker

Processa job in modo asincrono con garanzie di idempotenza.

Effect + Admin Loop

Applica l'effetto una sola volta oppure porta il job in failed per intervento operativo manuale auditato.

Stati del job

queued
Persistito, in attesa di essere processato.
in_progress
In esecuzione su un worker.
done
Effetto applicato con successo. Non verrà ripetuto.
failed
Errore permanente. Richiede intervento operativo manuale.

Scelte progettuali

Sistema volutamente minimale: niente retry automatici aggressivi, niente scheduling complesso, niente recupero automatico. Le feature vengono aggiunte solo quando necessarie, senza compromettere correttezza e auditabilità.

Scenari di failure

Evento duplicato

Scenario: Il provider invia lo stesso evento più volte.

bash
curl -s -X POST http://localhost:3000/events/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "evt_duplicate_demo_1",
    "event_type": "subscription.paid",
    "payload": { "subscription_id": "sub_123" }
  }'
# => {"accepted": true}

curl -s -X POST http://localhost:3000/events/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "evt_duplicate_demo_1",
    "event_type": "subscription.paid",
    "payload": { "subscription_id": "sub_123" }
  }'
# => {"accepted": true}

Comportamento: Le chiavi di idempotenza prevengono l'elaborazione duplicata. Solo il primo evento produce effetto.

Risultato: Lo stato cambia una sola volta. I duplicati restano visibili nel ledger per audit.

json
curl -s http://localhost:3000/admin/effects | jq
# => output
{
  "items": [
    {
      "id": "1",
      "idempotency_key": "activate_subscription:sub_123",
      "subscription_id": "sub_123",
      "status": "succeeded",
      "error_message": null,
      "created_at": "2026-02-09T10:32:29.301Z",
      "updated_at": "2026-02-09T10:32:29.303Z"
    }
  ],
  "limit": 50
}

Payload malformato

Scenario: Arriva un webhook con campi obbligatori mancanti.

bash
curl -s -X POST http://localhost:3000/events/ingest \
  -H "Content-Type: application/json" \
  -d '{
    "event_id": "evt_malformed_demo_1",
    "event_type": "subscription.paid",
    "payload": {}
  }'
# => {"accepted": true}

Comportamento: Il processing fallisce in modo deterministico. Il motivo del fallimento viene persistito.

json
curl -s http://localhost:3000/admin/jobs | jq
# => output
{
  "items": [
    {
      "id": "1",
      "status": "failed",
      "event_ledger_id": "1",
      "event_type": "subscription.paid",
      "external_event_id": "evt_malformed_demo_1",
      "attempts": 1,
      "max_attempts": 3,
      "failure_type": "permanent",
      "last_error": "Malformed payload: missing subscription_id",
      "created_at": "2026-02-09T10:42:36.035Z"
    }
  ],
  "limit": 50
}

Risultato: Nessun effetto parziale. Il job è visibile in stato failed e operabile.

Requeue Manuale

Scenario: Un operatore rimette in coda un job fallito dopo verifica.

bash
curl -s http://localhost:3000/admin/jobs | jq

Comportamento: Il requeue crea un record di audit (actor + reason + timestamp) e reinserisce il job.

json
{
  "items": [
    {
      "id": "1",
      "status": "failed",
      "attempts": 1,
      "failure_type": "permanent",
      "last_error": "Malformed payload: missing subscription_id"
    }
  ]
}
bash
curl -s -X POST http://localhost:3000/admin/jobs/1/requeue \
  -H "Content-Type: application/json" \
  -d '{
    "actor": "admin@example.com",
    "reason": "manual retry to requeue job"
  }' | jq
json
{
  "ok": true,
  "id": "1",
  "status": "queued",
  "available_at": "2026-02-09T10:51:45.416Z",
  "audit": {
    "id": "1",
    "action": "manual_requeue",
    "actor": "admin@example.com",
    "reason": "manual retry to requeue job",
    "created_at": "2026-02-09T10:51:45.416Z"
  }
}

Risultato: L'intervento manuale è completamente tracciabile. Il sistema resta spiegabile.

json
{
  "items": [
    {
      "audit": {
        "id": "1",
        "job_id": "1",
        "action": "manual_requeue",
        "actor": "admin@example.com",
        "reason": "manual retry to requeue job",
        "created_at": "2026-02-09T11:04:31.714Z"
      }
    }
  ],
  "limit": 50
}

Contattami

Se il tuo prodotto dipende da eventi esterni e serve correttezza operativa con retry, duplicati e consegna fuori ordine, posso aiutarti a progettarlo.

Contattami