API
Ricevi in tempo reale le vendite di un negozio che usa Omniflow POS. Ti serve una chiave, che ti consegna il negozio stesso: da lì fai tutto da solo.
Fatti dare la chiave
Il negozio la genera dal proprio pannello, in Impostazioni → Integrazioni, e te la
manda. Comincia con omf_live_ e si vede una volta sola: se va persa, il
negozio ne genera un'altra e la vecchia smette di valere.
Per controllare che funzioni:
curl https://pos.omniflow.it/api/v1/me \
-H "Authorization: Bearer omf_live_..."
{
"integration": "Delera",
"account": "Sartoria Carillo",
"webhook_url": null,
"events": ["vendita.chiusa"]
}
Registra il tuo indirizzo
Una chiamata sola. Nella risposta arriva il segreto con cui firmiamo ogni consegna: si vede lì e mai più, quindi conservalo subito.
curl -X POST https://pos.omniflow.it/api/v1/webhooks \
-H "Authorization: Bearer omf_live_..." \
-H "content-type: application/json" \
-d '{"url": "https://tuo-dominio.it/omniflow"}'
{
"url": "https://tuo-dominio.it/omniflow",
"secret": "il-segreto-per-verificare-la-firma",
"events": ["vendita.chiusa"],
"signature_header": "x-omniflow-signature",
"event_header": "x-omniflow-event"
}
Solo indirizzi https. Ripetendo la chiamata si registra un indirizzo
nuovo e si riceve un segreto nuovo: quello vecchio non vale più.
GET /v1/webhooks dice cosa risulta registrato,
DELETE /v1/webhooks stacca tutto.
Gli eventi disponibili sono vendita.chiusa, attivo di suo, e
vendita.annullata, che il negozio può accendere. Il nome dell'evento
viaggia nell'intestazione x-omniflow-event, mai nel corpo.
Ricevi le vendite
Alla chiusura di ogni scontrino chiamiamo il tuo indirizzo. Nessun invio giornaliero,
nessuna coda da interrogare: JSON piatto, con una sola eccezione — items,
le righe dello scontrino.
POST https://tuo-dominio.it/omniflow
content-type: application/json
x-omniflow-event: vendita.chiusa
x-omniflow-signature: sha256=<hmac del corpo>
{
"order_id": "9f1c2b7e-3f4a-4c21-9b77-2f0a1d8e5c33",
"customer_name": "Mario Rossi",
"customer_email": "mario.rossi@esempio.it",
"customer_phone": "+39 333 1234567",
"service_type": "sarta",
"total": 120.50,
"status": "completato",
"created_at": "2026-09-01T10:32:14.000Z",
"items": [
{
"descrizione": "Giacca sartoriale",
"quantita": 1,
"prezzo": 120.50,
"taglia": "48",
"sku": "GIA-48",
"barcode": "8012345678901",
"categoria": "Sartoria",
"tag": ["servizio"]
}
]
}
| Campo | Tipo | Note |
|---|---|---|
| order_id | string | Sempre presente. Identificativo univoco dello scontrino: è la chiave su cui deduplicare. |
| customer_name | string | Sempre presente. Senza anagrafica la vendita non parte affatto. |
| customer_email | string | Presente se il cliente ne ha una. Almeno uno fra i due recapiti c'è sempre. |
| customer_phone | string | Presente se il cliente ne ha uno. |
| service_type | string | Sempre valorizzato. Vedi sotto. |
| total | number | Totale incassato, in euro con due decimali. |
| status | string | completato, oppure annullato se il negozio accende quell'evento. |
| created_at | string | ISO 8601 in UTC. |
| items | array | Le righe dello scontrino: cosa ha comprato il cliente. Assente se lo scontrino non ne ha. |
| items[].descrizione | string | Sempre presente. Il nome dell'articolo come sta sullo scontrino. |
| items[].quantita | number | Sempre presente. |
| items[].prezzo | number | Sempre presente. Prezzo unitario in euro, non il totale di riga. |
| items[].taglia | string | La variante venduta. Presente se l'articolo ne ha una. |
| items[].sku | string | Presente se l'articolo ne ha uno. |
| items[].barcode | string | Presente se l'articolo ne ha uno. |
| items[].categoria | string | La categoria di catalogo. Un livello solo: non c'e' una macrocategoria. |
| items[].tag | array | I tag di catalogo dell'articolo. |
Il catalogo Omniflow ha una categoria per articolo, piu i tag: non esiste una
macrocategoria sopra di essa. Se il tuo gestionale ne vuole due livelli, scrivici:
va deciso insieme quale dei due esce da categoria e quale dai tag.
Tipo di servizio
Ogni vendita porta un tipo di servizio. Il valore parte esattamente com'è definito: minuscolo, senza accenti, senza traduzione — non lo normalizziamo mai. I valori ammessi sono questi tre, uguali per tutti i punti vendita collegati:
| Valore | Significato |
|---|---|
| standard | Vendita normale, senza lavorazione |
| sarta | Articolo venduto con un intervento di modifica su misura del cliente |
| su_misura | Articolo realizzato su misura per il cliente |
Quale dei tre esce lo decide ogni negozio dal proprio pannello, associando a ognuno le
categorie o le etichette del suo catalogo: due punti vendita diversi arrivano allo
stesso valore per strade diverse, e ognuno usa quelli che gli servono. Una vendita che
non corrisponde a nessuna regola parte come standard.
Verificare la firma
Ogni consegna porta x-omniflow-signature: l'HMAC-SHA256 del corpo, in
esadecimale, calcolato con il segreto ricevuto alla registrazione. Firma il
corpo grezzo, non il JSON riserializzato: uno spazio di differenza e la firma
non torna.
import { createHmac, timingSafeEqual } from 'node:crypto'
function firmaValida(corpoGrezzo, intestazione, segreto) {
const attesa = createHmac('sha256', segreto).update(corpoGrezzo).digest('hex')
const data = String(intestazione).replace(/^sha256=/, '')
const a = Buffer.from(attesa)
const b = Buffer.from(data)
return a.length === b.length && timingSafeEqual(a, b)
}
Chi preferisce qualcosa di più semplice può chiedere al negozio di configurare una API key o un Bearer token al posto della firma: li mandiamo in un'intestazione a scelta.
Come ci comportiamo
Idempotenza
Lo stesso scontrino non viene mai trasmesso due volte: la chiave è
order_id. Se lo ricevi due volte per un nostro ritentativo, scarta il
secondo.
Ritentativi
Se non rispondi o rispondi con un errore, la transazione resta registrata da noi con il suo payload e viene ritentata automaticamente alle vendite successive, per sette giorni. Il negozio può anche ripeterla a mano dal pannello.
Tempo di risposta
Chiudiamo la richiesta dopo cinque secondi. Oltre quel limite la consideriamo fallita e la ritentiamo: alla cassa c'è un cliente che aspetta lo scontrino, e non può aspettare il tuo server.
Vendite senza anagrafica
Molti scontrini al banco non hanno un cliente registrato. Senza nome e senza recapito non li inviamo: restano segnati da noi come ignorati. Se li vuoi comunque, il negozio può abilitarli.
Cosa risponderci
- 2xx — consegnata, non ritentiamo.
- 4xx — payload rifiutato: non ritentiamo e segnaliamo l'errore nel pannello del negozio. Un corpo con il motivo aiuta a capire cosa correggere.
- 5xx o nessuna risposta — problema temporaneo: ritentiamo.
Rileggere le transazioni
Se eri spento, o preferisci controllare invece di aspettare, le vendite già trasmesse si rileggono in qualsiasi momento.
curl "https://pos.omniflow.it/api/v1/transactions?since=2026-09-01T00:00:00Z&limit=50" \
-H "Authorization: Bearer omf_live_..."
{
"data": [ { "order_id": "...", "service_type": "sarta", "...": "..." } ],
"has_more": false
}
Ordine dal più recente. Ogni voce porta anche delivery_status e
sent_at, così sai se quella consegna era andata a buon fine.
Se qualcosa non torna
- 401 su ogni chiamata — la chiave è stata revocata o rigenerata: chiedine una nuova al negozio.
- 429 con
Retry-After: 60— hai superato le 120 chiamate al minuto concesse a una chiave. È il freno contro l'integrazione che va in ciclo: aspetta il minuto e riprendi. Se ti serve di più, scrivici. - Non arriva niente — verifica con
GET /v1/webhooksche l'indirizzo registrato sia quello giusto, e che risponda a unPOSTin meno di cinque secondi. - La firma non torna — quasi sempre è il corpo riserializzato: firma la stringa esatta ricevuta, prima di qualsiasi parsing.
- Vedi solo alcune vendite — quelle senza anagrafica cliente non partono, salvo che il negozio le abiliti.
Il negozio vede ogni consegna con esito, errore e payload, e può ripetere quelle fallite. Prima di scriverci, chiedigli cosa risulta lì.
Omniflow POS
Vai al sito