SaaS · fatturazione e CRM

Sincronizzazione da Stripe Billing al CRM: abbonamenti, pagamenti e MRR come deal, senza eventi persi o duplicati

Un'app da marketplace che replica clienti, abbonamenti e pagamenti Stripe nel CRM come contatti e deal, costruita su un registro di webhook firmati, row-level security per tenant e una riconciliazione giornaliera.

Settore
SaaS · fatturazione e CRM
Tecnologie
TypeScript · NestJS · PostgreSQL 16 + Drizzle

La sfida

Le aziende che fatturano tramite Stripe volevano che il loro CRM mostrasse chi paga, con quale piano e per quale valore mensile, senza aggiornamenti manuali. I webhook di Stripe non arrivano in ordine, possono arrivare due volte e portano un'istantanea che potrebbe essere già superata: una sincronizzazione che si fida di loro scrive fasi sbagliate e deal duplicati. L'app serve inoltre molte aziende contemporaneamente e ne custodisce le chiavi Stripe, quindi isolamento e gestione dei segreti facevano parte del brief fin dal primo giorno.

La soluzione

Abbiamo costruito un backend ospitato che esegue tre processi da un'unica immagine: API, worker e scheduler. Il punto di ingresso verifica la firma Stripe sul corpo grezzo della richiesta, identifica il tenant solo dal token dell'endpoint e risponde 2xx senza eseguire logica di business. Ogni evento finisce nel registro e in coda con un'unica transazione, con un outbox perché nulla vada perso se Redis si ferma. Prima di ogni scrittura, il worker recupera da Stripe l'oggetto aggiornato e scarta gli eventi superati, poi associa gli abbonamenti ai deal con fase, MRR, valuta e contatto. Una riconciliazione giornaliera recupera tutto ciò che i webhook hanno mancato.

Come si collega

  1. 01 Verifica del webhook
  2. 02 Registro e coda
  3. 03 Stato aggiornato da Stripe
  4. 04 Associazione al deal
  5. 05 Scrittura nel CRM
  6. 06 Riconciliazione giornaliera

Cosa abbiamo realizzato

  • Punto di ingresso dei webhook con verifica della firma sul corpo grezzo e risoluzione del tenant tramite il token dell'endpoint
  • Registro degli eventi con accodamento transazionale e un processo che svuota l'outbox
  • Code con una politica di nuovi tentativi, un circuit breaker e lock per oggetto
  • Associazione tra abbonamento e deal con fase, MRR (segnalato quando incompleto), valuta e contatti
  • Ricerca del deal che verifica la propria corrispondenza prima di scrivere
  • Importazione iniziale dello storico in blocchi riprendibili, più una riconciliazione giornaliera
  • Procedura guidata di configurazione in quattro passaggi e 11 lingue, che crea da sola i campi del CRM e le fasi della pipeline
  • Più account Stripe per utente, job di conservazione dei dati e statistiche sulle installazioni
  • Circa 590 casi di test automatizzati

Funzionalità

  • Row-level security di PostgreSQL su ogni tabella di business; le chiavi univoche includono il tenant e l'account Stripe
  • Cifratura a busta (envelope encryption) per i segreti salvati
  • I campi e le fasi impostati a mano nel CRM non vengono mai sovrascritti
  • Gli errori che un nuovo tentativo non può risolvere passano allo stato «richiede intervento» invece di essere ritentati all'infinito
  • La pagina di configurazione non carica script di terze parti, perché gli utenti vi incollano una chiave Stripe
  • Backup giornalieri, verificati con ripristini di prova
  • Log strutturati senza dati personali

Integrazioni

  • API e webhook di Stripe Billing
  • L'API CRM del cliente: contatti, deal, pipeline e campi personalizzati
  • Il marketplace di app del cliente

Tecnologie

  • TypeScript
  • NestJS (Node.js 22)
  • PostgreSQL 16 + Drizzle
  • Redis + BullMQ
  • Zod
  • pino
  • Docker
  • Nginx

Risultato

Distribuita su un server di staging ospitato e in collaudo con un account Stripe reale. Il collaudo e una revisione di sicurezza hanno rilevato dei difetti; quelli interni all'app sono stati corretti e ritestati. La pubblicazione nel marketplace seguirà il collaudo.

Perché leggere da Stripe prima di ogni scrittura?

Un webhook ti dice che qualcosa è cambiato, non che cosa è vero adesso. Due eventi sullo stesso abbonamento possono arrivare nell’ordine sbagliato, e il più vecchio sovrascriverebbe lo stato più recente. Per questo l’app legge dal webhook solo l’ID dell’evento, il suo tipo e l’ID dell’oggetto, poi recupera da Stripe l’oggetto aggiornato prima di scrivere qualsiasi cosa nel CRM. Grazie al registro un evento ripetuto non ha alcun effetto, e la riconciliazione giornaliera chiude ogni lacuna lasciata da una consegna mancata.

Parliamo del tuo progetto