Collegare altri programmi con API e webhook
Ricevi un webhook a ogni evento di Nanus (fattura emessa, DDT, ordine, fattura ricevuta…), leggi il registro degli eventi senza perderne nessuno e sincronizza ordini, documenti, clienti e articoli con una chiave API.
- Dove si trova
- Impostazioni → Integrazioni
A cosa serve
Le integrazioni collegano Nanus a un negozio online, a un CRM, a un programma per gli agenti o a un archivio su misura. Con un webhook è Nanus ad avvisare un altro programma a ogni evento scelto; con una chiave API è l’altro programma a leggere (e, se il ruolo lo permette, scrivere) i dati di Nanus.
- Ogni evento finisce nel registro degli eventi nello stesso momento in cui si salva il documento, con un numero progressivo per azienda: chi legge il registro li riceve tutti, in ordine e senza buchi.
- Gli eventi portano i dati completi: per un ordine il cliente con il codice, l’agente, il listino, il pagamento e ogni riga con l’articolo (codice, variante, categoria, codice a barre), le quantità evase e da evadere e gli importi.
- Gli eventi nascono anche da quello che Nanus fa da solo (conversioni, fatture ricorrenti, fatture arrivate dallo SDI, ordini dei negozi online): l’autore è allora
Nanus. - Il registro conserva gli eventi per 90 giorni; il registro delle consegne dei webhook per 30 giorni.
Le integrazioni le gestisce chi può modificare le impostazioni. I webhook ci sono sul cloud e sull’app desktop; le chiavi API solo sul cloud e solo per il titolare. La descrizione tecnica completa (OpenAPI 3.1) è all’indirizzo /v1/openapi.json del tuo Nanus, collegata anche da Specifica OpenAPI nella sezione Webhook.
Aggiungere un webhook
- Apri Impostazioni → Integrazioni e la sezione Webhook.
- In Indirizzo che riceve scrivi l’URL del programma che riceverà gli avvisi e, se vuoi, una Descrizione.
- In Eventi da mandare apri i gruppi che ti servono (Fatture emesse, DDT, Ordini dei clienti, Fatture ricevute dallo SDI…) e spunta i singoli eventi, oppure Tutto il gruppo. Tutti e Nessuno spuntano o tolgono l’intero catalogo.
- Tocca Aggiungi webhook e copia subito il segreto mostrato nel programma che riceve: poi se ne vedranno solo le ultime cifre.
- Scegli nel menu accanto al webhook un evento (per esempio Ordine creato) o lascia Messaggio di prova, poi tocca Prova: Nanus manda un esempio realistico di quell’evento e mostra la risposta.
- Modifica riapre indirizzo, descrizione ed eventi; Salva modifiche li aggiorna senza cambiare il segreto.
- Sospendi e Riattiva fermano e riprendono gli invii.
- Nuovo segreto ne genera un altro: quello attuale smette subito di valere.
- Elimina toglie il webhook.
Consegne, nuovi tentativi e Invia di nuovo
A ogni evento Nanus manda una richiesta POST con l’evento in JSON. La consegna è riuscita quando l’indirizzo risponde con un codice 2xx entro 10 secondi. Le intestazioni dicono cosa arriva:
Nanus-Evento: il tipo, per esempioordine.creato.Nanus-Evento-IdeNanus-Progressivo: l’id unico dell’evento e il suo numero nel registro. Un evento può arrivare più di una volta (dopo un errore di rete o con Invia di nuovo): usa l’id per scartare i doppioni.Nanus-ConsegnaeNanus-Tentativo: la consegna e il numero del tentativo.Nanus-Firma:sha256=seguito dalla firma HMAC-SHA256 del corpo fatta con il segreto.
Se l’indirizzo non risponde o risponde con un errore, Nanus riprova dopo 30 secondi, 2 minuti, 10 minuti, 1 ora, 3, 6, 12 e 24 ore. I tentativi sono salvati nel registro: riprendono anche dopo un riavvio del server o dell’app desktop.
- Consegne apre il Registro delle consegne (ultimi 30 giorni): per ogni evento lo stato (Consegnata, In attesa, Non riuscita), i tentativi, il codice HTTP, l’inizio della risposta e l’ora del nuovo tentativo.
- Invia di nuovo rimanda subito una consegna conclusa, riuscita o no.
Dopo 10 consegne non riuscite di fila il webhook si disattiva e nell’elenco compare l’avviso Disattivato. Controlla l’indirizzo e tocca Riattiva: le consegne mancate restano nel registro e puoi mandarle con Invia di nuovo, oppure recuperarle da /v1/eventi.
Verificare la firma
Calcola l’HMAC-SHA256 del corpo così come arriva (prima di leggerlo come JSON) con il segreto del webhook e confrontalo con Nanus-Firma, con un confronto a tempo costante. Se non coincide rispondi 401 e non usare i dati.
Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto'
import { createServer } from 'node:http'
const segreto = process.env.NANUS_SEGRETO
createServer((req, res) => {
const pezzi = []
req.on('data', (p) => pezzi.push(p))
req.on('end', () => {
const corpo = Buffer.concat(pezzi)
const firma = Buffer.from(req.headers['nanus-firma'] ?? '')
const attesa = Buffer.from(
'sha256=' + createHmac('sha256', segreto).update(corpo).digest('hex'),
)
if (firma.length !== attesa.length || !timingSafeEqual(firma, attesa)) {
return res.writeHead(401).end()
}
const evento = JSON.parse(corpo)
// evento.id è unico: se l’hai già trattato, rispondi 200 e basta
console.log(evento.progressivo, evento.evento, evento.oggetto.id)
res.writeHead(200).end('ok')
})
}).listen(3000)Python (Flask):
import hashlib, hmac, os
from flask import Flask, abort, request
app = Flask(__name__)
SEGRETO = os.environ["NANUS_SEGRETO"].encode()
@app.post("/nanus")
def ricevi():
corpo = request.get_data()
attesa = "sha256=" + hmac.new(SEGRETO, corpo, hashlib.sha256).hexdigest()
if not hmac.compare_digest(attesa, request.headers.get("Nanus-Firma", "")):
abort(401)
evento = request.get_json()
print(evento["progressivo"], evento["evento"], evento["oggetto"]["id"])
return "ok"PHP:
<?php
$segreto = getenv('NANUS_SEGRETO');
$corpo = file_get_contents('php://input');
$attesa = 'sha256=' . hash_hmac('sha256', $corpo, $segreto);
$firma = $_SERVER['HTTP_NANUS_FIRMA'] ?? '';
if (!hash_equals($attesa, $firma)) {
http_response_code(401);
exit;
}
$evento = json_decode($corpo, true);
error_log($evento['progressivo'] . ' ' . $evento['evento']);
http_response_code(200);
Catalogo degli eventi
I nomi hanno la forma oggetto.azione. Gli eventi creato, modificato ed eliminato (o al femminile) ci sono per ogni oggetto; una modifica che non cambia niente non genera eventi.
- Fatture emesse (anche note di credito, parcelle e autofatture):
fattura.creata,fattura.modificata,fattura.eliminata,fattura.inviataSdi,fattura.esitoSdi. - DDT:
ddt.creato,ddt.modificato,ddt.eliminato,ddt.fatturato. - Ordini dei clienti:
ordine.creato,ordine.modificato,ordine.eliminato,ordine.statoCambiato,ordine.lavorazioneCambiata,ordine.evasoParzialmente,ordine.evaso,ordine.annullato.ordine.statoCambiatoriguarda lo stato di evasione (stato, calcolato dalle consegne);ordine.lavorazioneCambiatala lavorazione scelta dall’utente (lavorazione:ricevuto,confermato,inAttesaCompletamento,spedito), anche quando Nanus segnaspeditodopo il DDT che consegna tutto. Entrambi arrivano insieme aordine.modificato. - Preventivi:
preventivo.creato,preventivo.modificato,preventivo.eliminato,preventivo.inviato,preventivo.accettato,preventivo.rifiutato,preventivo.convertito. - Ordini ai fornitori:
ordineFornitore.creato,ordineFornitore.modificato,ordineFornitore.eliminato,ordineFornitore.ricevutoParzialmente,ordineFornitore.ricevuto,ordineFornitore.annullato. - Arrivi della merce:
arrivo.registrato,arrivo.modificato,arrivo.eliminato. - Fatture ricevute dallo SDI:
fatturaRicevuta.arrivata,fatturaRicevuta.registrata,fatturaRicevuta.scartata,fatturaRicevuta.eliminata. - Acquisti:
acquisto.registrato,acquisto.modificato,acquisto.eliminato;acquisto.ricevutoPeceacquisto.ricevutoarrivano insieme afatturaRicevuta.arrivataper i programmi già collegati. - Incassi e pagamenti:
pagamento.registrato,pagamento.annullato. - Scadenzario:
scadenza.creata,scadenza.modificata,scadenza.eliminata,scadenza.saldata. - Distinte RiBa e SDD:
distinta.presentata,distinta.modificata,distinta.accreditata,distinta.insoluto,distinta.eliminata. - Movimenti di magazzino:
movimento.creato,movimento.modificato,movimento.eliminato. - Clienti e fornitori, articoli, listini:
anagrafica.creata,anagrafica.modificata,anagrafica.eliminata,articolo.creato,articolo.modificato,articolo.eliminato,listino.creato,listino.modificato,listino.eliminato.
Ogni evento ha la stessa busta: id, progressivo, creatoIl, versione (oggi 1), autore (utente, chiave o nanus), evento, oggetto (tipo e id) e dati, la fotografia completa del documento dopo il salvataggio (prima dell’eliminazione per gli eventi eliminato). Gli importi sono testi con 2 decimali, le date nel formato AAAA-MM-GG. Ecco ordine.creato, accorciato a una riga:
{
"id": "0192f2c4-5b1e-7a3d-9f60-2d4e8c1a7b90",
"progressivo": 1842,
"creatoIl": "2026-10-28T09:14:03.512Z",
"versione": 1,
"autore": { "tipo": "utente", "id": "u1", "nome": "Anna Verdi" },
"evento": "ordine.creato",
"oggetto": { "tipo": "ordine", "id": "0192f0a1-0000-7000-8000-000000100001" },
"dati": {
"id": "0192f0a1-0000-7000-8000-000000100001",
"numero": "2026/158",
"data": "2026-10-28",
"stato": "daEvadere",
"lavorazione": "confermato",
"lavorazioneDal": "2026-10-28",
"deposito": "principale",
"cliente": {
"id": "0192f0a1-0000-7000-8000-00000000c001",
"codice": "C0042",
"denominazione": "Officine Rossi S.r.l.",
"partitaIva": "12345678903",
"indirizzo": { "indirizzo": "Via Industria 12", "cap": "25121", "comune": "Brescia", "provincia": "BS", "nazione": "IT" }
},
"destinazione": { "id": "d1", "descrizione": "Magazzino di Rezzato", "indirizzo": { "indirizzo": "Via dei Mille 4", "cap": "25086", "comune": "Rezzato", "provincia": "BS", "nazione": "IT" } },
"agente": { "id": "0192f0a1-0000-7000-8000-0000000a9e01", "codice": "AG03", "nome": "Marco Bianchi" },
"listino": { "codice": "RIV", "descrizione": "Rivenditori" },
"pagamento": { "codice": "RB60", "descrizione": "Ricevuta bancaria 60 gg DF FM", "modalita": "MP12" },
"consegnaPrevista": "2026-11-12",
"riferimentoCliente": "ODA 4471",
"totali": { "imponibile": "666.00", "imposta": "146.52", "totale": "812.52" },
"ddt": [],
"fatture": [],
"righe": [
{
"numero": 1,
"articolo": {
"codice": "FL-120",
"descrizione": "Flangia in acciaio DN120",
"variante": { "codice": "ZN", "descrizione": "Zincata" },
"categoria": "Flange",
"codiceBarre": "8001234567901"
},
"descrizione": "Flangia in acciaio DN120 zincata",
"unitaMisura": "PZ",
"quantita": "40",
"quantitaEvasa": "0",
"quantitaResidua": "40",
"prezzoUnitario": "18.50",
"sconti": [{ "tipo": "SC", "percentuale": "10", "importo": null }],
"prezzoNetto": "16.65",
"importo": "666.00",
"aliquotaIva": "22"
}
]
}
}Un esempio completo e realistico di ogni evento del catalogo lo leggi con GET /v1/eventi/esempi/<evento> (per esempio /v1/eventi/esempi/fatturaRicevuta.arrivata) e nella sezione webhooks della specifica OpenAPI. DDT, fatture e fatture ricevute hanno lo stesso livello di dettaglio dell’ordine; i campi senza valore valgono null.
Leggere il registro degli eventi
Con una chiave API leggi gli eventi in ordine di progressivo con GET /v1/eventi?dopo=<progressivo>&tipi=<eventi separati da virgola>&limite=<fino a 1000>. La risposta contiene dati (gli eventi), prossimo (il valore da passare come dopo alla chiamata successiva), ultimo (l’ultimo progressivo scritto) e incompleto.
- La prima volta chiama
/v1/eventi?dopo=0. - Tratta gli eventi ricevuti e salva
prossimocome segnalibro. - Richiama con
dopouguale al segnalibro: quandoprossimoè uguale aultimosei in pari, aspetta un po’ prima di chiedere ancora.
Il registro non salta mai un numero e non restituisce eventi che si stanno ancora scrivendo: un programma che segue prossimo non perde nulla. Se il segnalibro è più vecchio di 90 giorni incompleto vale true: rifai una sincronizzazione completa con le API qui sotto. La chiave vede solo gli eventi delle aree che il suo ruolo può leggere.
Sincronizzare i dati
Per la prima copia dei dati, o per ricontrollarli, usa GET /v1/sincronizzazione/<risorsa>: le risorse sono ordini, preventivi, ddt, fatture, ricevute (fatture ricevute dallo SDI), acquisti, anagrafiche e articoli. Ogni record ha la stessa forma dei dati degli eventi; GET /v1/sincronizzazione/<risorsa>/<id> ne legge uno con la data dell’ultima modifica.
modificatoDal: solo i record modificati da quell’istante (data e ora ISO, per esempio2026-10-01T00:00:00Z).daleal: data del documento (AAAA-MM-GG).stato: per esempiodaEvadere,parziale,evasoper gli ordini.lavorazione: solo per gli ordini,ricevuto,confermato,inAttesaCompletamentoospedito(per esempio/v1/sincronizzazione/ordini?lavorazione=confermato).clienteeagente: id o codice, per esempiocliente=C0042oagente=AG03.limite(fino a 500) edopo: la pagina successiva si chiede passando comedopoil valoreprossimodella risposta, finché ènull.
Esempio: storico degli ordini riga per riga
Un programma esterno vuole una tabella con una riga per ogni riga d’ordine: numero e data dell’ordine, cliente, agente, articolo, quantità ordinata ed evasa, importo. La tiene aggiornata così:
- Crea in Chiavi API una chiave con ruolo Consultazione: legge tutto e non può modificare niente.
- Leggi
ultimoda/v1/eventi?limite=1e tienilo come segnalibro. - Scarica tutti gli ordini da
/v1/sincronizzazione/ordini, pagina per pagina, e scrivi una riga della tabella per ogni riga d’ordine. - Da lì in poi leggi
/v1/eventidal segnalibro con i soli eventi degli ordini: per ogni evento sostituisci le righe di quell’ordine con quelle nuove (perordine.eliminatocancellale). - In alternativa al punto 4, aggiungi un webhook con il gruppo Ordini dei clienti: ricevi gli stessi eventi appena succedono. Conviene comunque tenere il segnalibro per recuperare quelli persi mentre il programma era spento.
// Node.js 18 o successivo: NANUS_API = indirizzo dell’API, NANUS_CHIAVE = chiave
const leggi = (percorso) =>
fetch(process.env.NANUS_API + percorso, {
headers: { authorization: 'Bearer ' + process.env.NANUS_CHIAVE },
}).then((r) => r.json())
const storico = new Map() // id ordine → righe della tabella
const aggiorna = (o) =>
storico.set(o.id, o.righe.map((r) => ({
ordine: o.numero, data: o.data, stato: o.stato,
cliente: o.cliente.codice, agente: o.agente?.codice ?? null,
riga: r.numero, articolo: r.articolo?.codice ?? null,
descrizione: r.descrizione, quantita: r.quantita,
evasa: r.quantitaEvasa, importo: r.importo,
})))
let { ultimo: segnalibro } = await leggi('/eventi?limite=1')
let pagina = ''
do {
const r = await leggi('/sincronizzazione/ordini?limite=500&dopo=' + pagina)
r.dati.forEach(aggiorna)
pagina = r.prossimo
} while (pagina)
const tipi = ['creato', 'modificato', 'statoCambiato', 'evasoParzialmente',
'evaso', 'annullato', 'eliminato'].map((a) => 'ordine.' + a).join(',')
for (;;) {
const r = await leggi('/eventi?limite=500&tipi=' + tipi + '&dopo=' + segnalibro)
for (const e of r.dati) {
if (e.evento === 'ordine.eliminato') storico.delete(e.oggetto.id)
else aggiorna(e.dati)
}
segnalibro = r.prossimo // salvalo: alla ripartenza riprendi da qui
if (segnalibro === r.ultimo) await new Promise((ok) => setTimeout(ok, 60_000))
}
Creare una chiave API (cloud)
- Nella sezione Chiavi API scrivi un Nome (per ricordare chi la usa).
- Scegli il Ruolo: la chiave lavora con i permessi di quel ruolo (Collaboratore, Amministrazione, Commerciale, Magazzino o Consultazione). Per un programma che legge soltanto scegli Consultazione.
- Tocca Crea chiave e copia subito la chiave: per sicurezza non si potrà più rivedere.
- Nel programma esterno usa l’indirizzo dell’API indicato nella nota della sezione, con l’intestazione
Authorization: Bearerseguita dalla chiave.
Una chiave non può gestire collaboratori né altre chiavi. Nell’elenco vedi l’inizio della chiave e la data dell’ultimo uso; Revoca la disattiva subito.
Cosa controlla Nanus
- L’indirizzo del webhook deve essere un URL http o https.
- Sul cloud il webhook deve usare https verso un indirizzo pubblico: niente localhost, indirizzi IP o reti interne. Sull’app desktop puoi indicare anche un programma della tua rete.
- Una chiave legge eventi e dati solo delle aree permesse al suo ruolo: per esempio una chiave Magazzino non vede incassi e scadenze.
Domande frequenti
Ho perso una chiave API, posso rivederla?
No. Revocala e creane un’altra, poi aggiorna il programma che la usava.
Le operazioni fatte con una chiave si vedono?
Sì: compaiono nel registro attività come quelle delle persone, e negli eventi l’autore è la chiave.
Posso ricevere due volte lo stesso evento?
Sì, per esempio dopo un errore di rete o con Invia di nuovo. L’intestazione Nanus-Evento-Id (e il campo id) è sempre la stessa: se l’hai già trattato, rispondi 200 e ignoralo.
Il programma che riceve è rimasto spento per un giorno: perdo gli eventi?
No. Nanus ritenta per circa due giorni e le consegne restano nel registro per 30 giorni; gli eventi si leggono da /v1/eventi per 90 giorni.