Event Processing Affidabile (B2B SaaS)
Un layer affidabile per webhook e integrazioni SaaS: processa eventi esterni in sicurezza, evita duplicazioni e mantiene osservabilità operativa.
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
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.
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.
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.
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.
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.
curl -s http://localhost:3000/admin/jobs | jqComportamento: Il requeue crea un record di audit (actor + reason + timestamp) e reinserisce il job.
{
"items": [
{
"id": "1",
"status": "failed",
"attempts": 1,
"failure_type": "permanent",
"last_error": "Malformed payload: missing subscription_id"
}
]
}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{
"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.
{
"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