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"const { companies } = await pbx.exofattura.companies({
apiKey: process.env.EXOFATTURA_API_KEY!,
environment: 'production',
});
const { integration } = await pbx.exofattura.connect({
apiKey: process.env.EXOFATTURA_API_KEY!,
environment: 'production',
companyId: companies[0]!.id,
});
console.log(integration?.status, integration?.mode, integration?.stats.contacts);$out = $pbx->exofattura->connect([
'apiKey' => getenv('EXOFATTURA_API_KEY'),
'environment' => 'production',
'companyId' => 7,
]);
echo $out['integration']['status'], ' ', $out['integration']['mode'], PHP_EOL;import os
out = pbx.exofattura.connect(
{"apiKey": os.environ["EXOFATTURA_API_KEY"], "environment": "production", "companyId": 7}
)
print(out["integration"]["status"], out["integration"]["mode"])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'è solokeyHint(ultimi caratteri). Per modificare il collegamento senza cambiare la chiave si ometteapiKey.environment:production(https://api.exofattura.it) otest(https://api-test.exofattura.it). Sono gli unici indirizzi ammessi: non si possono indicare URL liberi.companyId: azienda di ExoFattura (headerX-Company-Id).POST /v1/integrations/exofattura/companiesrestituisce le aziende disponibili per una chiave, per proporle in un menu; conavailable: falsel'id si indica a mano.- Chiave non valida, revocata o dell'ambiente sbagliato:
400 VALIDATION_ERRORcondetails[].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
| ExoFattura | Contatto della rubrica aziendale |
|---|---|
business_name (aziende) / first_name + last_name (privati) | name |
phone | numero con etichetta work (aziende) o home (privati) |
mobile_phone | numero 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.updatedecustomer.deleted. Al collegamento ExoVoIP registra da solo il webhook in ExoFattura (quando gli eventi cliente risultano disponibili inGET /api/v1/webhooks/events); in alternativa si registra a mano l'indirizzointegration.webhook.urle si salva il segreto di firma conPUT /v1/integrations/exofattura(webhookSecret). Con webhook e segreto attiviintegration.modevalerealtime. - Ogni 15 minuti: lettura incrementale dei soli clienti modificati (
updated_sincesull'updated_atpiù 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/syncaccoda 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"await pbx.exofattura.sync({ confirmDeletions: false });
const { items } = await pbx.exofattura.log({ limit: 10 });
for (const run of items)
console.log(run.startedAt, run.status, run.created, run.updated, run.deleted);$pbx->exofattura->sync(['confirmDeletions' => false]);
foreach ($pbx->exofattura->log(10)['items'] as $run) {
echo $run['startedAt'], ' ', $run['status'], PHP_EOL;
}pbx.exofattura.sync({"confirmDeletions": False})
for run in pbx.exofattura.log(limit=10)["items"]:
print(run["startedAt"], run["status"], run["created"], run["updated"], run["deleted"])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:
| Valore | Significato |
|---|---|
connected | aggiornamenti attivi |
error | ultimo aggiornamento non riuscito (ExoFattura non raggiungibile): si riprova da solo |
reconnect | chiave 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 (401se assente o non valida); - deduplica su
event_id(stesso evento ricevuto più volte = elaborato una volta); - il cliente nel
payload.dataviene usato direttamente; solo se manca si rileggeGET /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.testrisponde200; 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
| Descrizione | Richiesta | Scope | Metodo 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 importati | GET /v1/integrations/exofattura | contacts:read | exofattura.get() |
| Collega o modifica il collegamento (chiave verificata prima di salvare); il primo collegamento avvia l’import completo | PUT /v1/integrations/exofattura | integrations:write | exofattura.connect() |
| Scollega ExoFattura (deleteContacts=true elimina anche i contatti importati) | DELETE /v1/integrations/exofattura | integrations:write | exofattura.disconnect() |
| Aziende ExoFattura disponibili per una chiave (senza apiKey: chiave salvata) | POST /v1/integrations/exofattura/companies | integrations:write | exofattura.companies() |
| Prova del collegamento salvato (chiave, azienda, eventi cliente disponibili) | POST /v1/integrations/exofattura/test | integrations:write | exofattura.test() |
| Sincronizza ora (aggiornamento completo in coda) | POST /v1/integrations/exofattura/sync | integrations:write | exofattura.sync() |
| Esiti degli ultimi aggiornamenti | GET /v1/integrations/exofattura/log | contacts:read | exofattura.log() |