ExoVoIP Manuale sviluppatori

Integrazione ExoFattura

Con il plugin exofattura i clienti di ExoFattura (privati e aziende) entrano nella rubrica aziendale del centralino e restano aggiornati: quando un cliente chiama, sul telefono compare il suo nome. L'integrazione è un plugin a pagamento attivato da Exobyte: senza il plugin gli endpoint rispondono 403 PLUGIN_REQUIRED (lo scollegamento resta sempre possibile). Lettura con scope contacts:read, gestione con integrations:write (amministratore dell'azienda; il superadmin opera con X-Tenant-Id).

Collegamento

# Collega ExoFattura (la chiave viene verificata prima di salvare e non viene più restituita)
curl -s -X PUT https://centralino.exovoip.it/api/v1/integrations/exofattura \
  -H "X-API-Key: $EXOPBX_API_KEY" -H "Content-Type: application/json" \
  -d '{"apiKey":"'"$EXOFATTURA_API_KEY"'","environment":"production","companyId":7}'
# Stato: ultimo aggiornamento, clienti importati, modalità (realtime / polling)
curl -s https://centralino.exovoip.it/api/v1/integrations/exofattura -H "X-API-Key: $EXOPBX_API_KEY"
  • apiKey: chiave API di ExoFattura (fat_… per la produzione, fat_test_… per l'ambiente di prova). Viene verificata con una chiamata di prova prima del salvataggio, conservata cifrata e mai restituita: nelle risposte c'è solo keyHint (ultimi caratteri). Per modificare il collegamento senza cambiare la chiave si omette apiKey.
  • environment: production (https://api.exofattura.it) o test (https://api-test.exofattura.it). Sono gli unici indirizzi ammessi: non si possono indicare URL liberi.
  • companyId: azienda di ExoFattura (header X-Company-Id). POST /v1/integrations/exofattura/companies restituisce le aziende disponibili per una chiave, per proporle in un menu; con available: false l'id si indica a mano.
  • Chiave non valida, revocata o dell'ambiente sbagliato: 400 VALIDATION_ERROR con details[].path = ["apiKey"]; azienda non trovata: path = ["companyId"]; ExoFattura non raggiungibile: 503 SERVICE_UNAVAILABLE.

Il primo collegamento avvia subito l'import completo. Cambiando azienda o ambiente i contatti importati finora vengono sostituiti da quelli della nuova azienda.

Come vengono importati i clienti

ExoFatturaContatto della rubrica aziendale
business_name (aziende) / first_name + last_name (privati)name
phonenumero con etichetta work (aziende) o home (privati)
mobile_phonenumero con etichetta mobile
email (email commerciale, mai la PEC)email
—tag Cliente, source: "exofattura", company: null

I numeri sono normalizzati in formato internazionale (+39…); quelli non validi vengono ignorati e i clienti senza alcun numero valido non vengono importati. Nessun dato fiscale (partita IVA, codice fiscale, PEC) entra nella rubrica.

Contatti inseriti a mano

I contatti creati a mano, da CSV, vCard o API non vengono mai modificati, collegati né eliminati dall'integrazione, anche se hanno lo stesso numero di un cliente: i clienti di ExoFattura sono contatti separati con source: "exofattura". Allo stesso modo un import CSV / vCard non aggiorna mai un contatto importato da ExoFattura.

Contatti importati

Nome, azienda, email e numeri di un contatto exofattura sono gestiti dalla sincronizzazione: PATCH /v1/contacts/:id che li cambia risponde 400 VALIDATION_ERROR (valori invariati ammessi). vip, blocked, tags e notes restano modificabili e vengono mantenuti a ogni aggiornamento. Un contatto importato eliminato a mano ritorna al giro successivo finché il cliente esiste in ExoFattura.

Aggiornamento

  • In tempo reale: ExoFattura invia gli eventi customer.created, customer.updated e customer.deleted. Al collegamento ExoVoIP registra da solo il webhook in ExoFattura (quando gli eventi cliente risultano disponibili in GET /api/v1/webhooks/events); in alternativa si registra a mano l'indirizzo integration.webhook.url e si salva il segreto di firma con PUT /v1/integrations/exofattura (webhookSecret). Con webhook e segreto attivi integration.mode vale realtime.
  • Ogni 15 minuti: lettura incrementale dei soli clienti modificati (updated_since sull'updated_at più alto ricevuto), sempre attiva come rete di sicurezza.
  • Riconciliazione notturna: una volta al giorno (di notte, comunque entro 36 ore) la lettura completa elimina i contatti dei clienti non più presenti, per recuperare eventuali eliminazioni perse.
  • Sincronizza ora: POST /v1/integrations/exofattura/sync accoda subito una lettura completa.
# Aggiornamento completo subito; confirmDeletions applica le eliminazioni sospese dalla soglia
curl -s -X POST https://centralino.exovoip.it/api/v1/integrations/exofattura/sync \
  -H "X-API-Key: $EXOPBX_API_KEY" -H "Content-Type: application/json" \
  -d '{"confirmDeletions":false}'
# Esiti degli ultimi aggiornamenti
curl -s "https://centralino.exovoip.it/api/v1/integrations/exofattura/log?limit=10" -H "X-API-Key: $EXOPBX_API_KEY"

Soglia di sicurezza

Dopo una lettura completa, se i contatti da eliminare sono più della metà di quelli importati (o se ExoFattura restituisce un elenco vuoto) le eliminazioni non vengono applicate: l'aggiornamento risulta blocked, integration.pendingDeletions indica quante sono e lastError lo spiega. Per applicarle: POST /v1/integrations/exofattura/sync con {"confirmDeletions": true}.

Stato e registro

GET /v1/integrations/exofattura restituisce connected e integration (ambiente, azienda, keyHint, status, mode, webhook, ultimo e prossimo aggiornamento, statistiche). status:

ValoreSignificato
connectedaggiornamenti attivi
errorultimo aggiornamento non riuscito (ExoFattura non raggiungibile): si riprova da solo
reconnectchiave revocata o non più valida: aggiornamenti fermi finché non si incolla una nuova chiave

GET /v1/integrations/exofattura/log elenca gli esiti degli ultimi aggiornamenti (trigger: connect, schedule, manual, webhook; contatori di clienti letti, contatti creati, aggiornati, eliminati). POST /v1/integrations/exofattura/test verifica chiave e azienda salvate.

Webhook in entrata

ExoFattura chiama POST /v1/integrations/exofattura/webhook/:token (pubblico, token casuale dell'azienda nell'indirizzo):

  • firma X-Exobyte-Signature: sha256=<hex>: HMAC-SHA256 del corpo grezzo con il segreto dell'endpoint, verificata a tempo costante (401 se assente o non valida);
  • deduplica su event_id (stesso evento ricevuto più volte = elaborato una volta);
  • il cliente nel payload.data viene usato direttamente; solo se manca si rilegge GET /api/v1/customers/{id};
  • gli eventi arrivati fuori ordine si ordinano con occurred_at / updated_at: un aggiornamento più vecchio di quello già applicato, o di un'eliminazione, viene ignorato;
  • webhook.test risponde 200; la risposta arriva subito, il lavoro è in coda.

Scollegamento

DELETE /v1/integrations/exofattura ferma gli aggiornamenti e rimuove il webhook registrato in ExoFattura. Con ?deleteContacts=true elimina anche i contatti importati; senza, i contatti restano in rubrica come contatti normali e diventano modificabili.

Endpoint

DescrizioneRichiestaScopeMetodo SDK
Webhook in entrata da ExoFattura (eventi cliente, firma HMAC, pubblico)POST /v1/integrations/exofattura/webhook/:token—exofattura.webhook()
Stato del collegamento con ExoFattura (mai la chiave): ambiente, azienda, modalità, ultimo aggiornamento, clienti importatiGET /v1/integrations/exofatturacontacts:readexofattura.get()
Collega o modifica il collegamento (chiave verificata prima di salvare); il primo collegamento avvia l’import completoPUT /v1/integrations/exofatturaintegrations:writeexofattura.connect()
Scollega ExoFattura (deleteContacts=true elimina anche i contatti importati)DELETE /v1/integrations/exofatturaintegrations:writeexofattura.disconnect()
Aziende ExoFattura disponibili per una chiave (senza apiKey: chiave salvata)POST /v1/integrations/exofattura/companiesintegrations:writeexofattura.companies()
Prova del collegamento salvato (chiave, azienda, eventi cliente disponibili)POST /v1/integrations/exofattura/testintegrations:writeexofattura.test()
Sincronizza ora (aggiornamento completo in coda)POST /v1/integrations/exofattura/syncintegrations:writeexofattura.sync()
Esiti degli ultimi aggiornamentiGET /v1/integrations/exofattura/logcontacts:readexofattura.log()