Vai al contenuto

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

Apri in Nanus

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

  1. Apri Impostazioni → Integrazioni e la sezione Webhook.
  2. In Indirizzo che riceve scrivi l’URL del programma che riceverà gli avvisi e, se vuoi, una Descrizione.
  3. 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.
  4. Tocca Aggiungi webhook e copia subito il segreto mostrato nel programma che riceve: poi se ne vedranno solo le ultime cifre.
  5. 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 esempio ordine.creato.
  • Nanus-Evento-Id e Nanus-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-Consegna e Nanus-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.statoCambiato riguarda lo stato di evasione (stato, calcolato dalle consegne); ordine.lavorazioneCambiata la lavorazione scelta dall’utente (lavorazione: ricevuto, confermato, inAttesaCompletamento, spedito), anche quando Nanus segna spedito dopo il DDT che consegna tutto. Entrambi arrivano insieme a ordine.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.ricevutoPec e acquisto.ricevuto arrivano insieme a fatturaRicevuta.arrivata per 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.

  1. La prima volta chiama /v1/eventi?dopo=0.
  2. Tratta gli eventi ricevuti e salva prossimo come segnalibro.
  3. Richiama con dopo uguale al segnalibro: quando prossimo è uguale a ultimo sei 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 esempio 2026-10-01T00:00:00Z).
  • dal e al: data del documento (AAAA-MM-GG).
  • stato: per esempio daEvadere, parziale, evaso per gli ordini.
  • lavorazione: solo per gli ordini, ricevuto, confermato, inAttesaCompletamento o spedito (per esempio /v1/sincronizzazione/ordini?lavorazione=confermato).
  • cliente e agente: id o codice, per esempio cliente=C0042 o agente=AG03.
  • limite (fino a 500) e dopo: la pagina successiva si chiede passando come dopo il valore prossimo della 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ì:

  1. Crea in Chiavi API una chiave con ruolo Consultazione: legge tutto e non può modificare niente.
  2. Leggi ultimo da /v1/eventi?limite=1 e tienilo come segnalibro.
  3. Scarica tutti gli ordini da /v1/sincronizzazione/ordini, pagina per pagina, e scrivi una riga della tabella per ogni riga d’ordine.
  4. Da lì in poi leggi /v1/eventi dal segnalibro con i soli eventi degli ordini: per ogni evento sostituisci le righe di quell’ordine con quelle nuove (per ordine.eliminato cancellale).
  5. 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)

  1. Nella sezione Chiavi API scrivi un Nome (per ricordare chi la usa).
  2. 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.
  3. Tocca Crea chiave e copia subito la chiave: per sicurezza non si potrà più rivedere.
  4. Nel programma esterno usa l’indirizzo dell’API indicato nella nota della sezione, con l’intestazione Authorization: Bearer seguita 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.

Vedi anche