Ricevere le stampe sul tuo server
Ultimo aggiornamento:
Con l'abbonamento mensile o annuale, iiziticket può inviare ogni stampa a un indirizzo web scelto da te: il tuo gestionale, uno script, Zapier, Make, n8n, Google Apps Script. Ricevi un elemento per ogni copia stampata, con i valori del modulo e i numeri dei contatori. I totali, i riepiloghi e tutto il resto li calcoli tu con i tuoi servizi.
L'invio parte dal telefono o dal tablet che stampa: i dati non passano dai server di iiziticket.
In breve
- Una richiesta
POSTin JSON per ogni stampa, con un elemento per copia. - Firmata con HMAC-SHA256: il tuo server verifica che arrivi dal tuo dispositivo.
- Se il server non risponde, la stampa resta in coda e riparte; la stampa non aspetta mai l'invio.
- Si configura nell'app: Impostazioni → Integrazioni.
Configurazione
- Indirizzo (URL): dove ricevere le stampe. Deve iniziare con
https://. Per un server nella rete del negozio è consentitohttp://con indirizzi locali (192.168.x.x,10.x.x.x,172.16–31.x.x,*.local). - Invio continuo: acceso, ogni stampa parte subito dopo essere uscita; spento, le stampe restano in coda finché premi Invia ora.
- Nome del dispositivo: arriva con ogni stampa (es. "Cassa 1"), utile se stampi da più dispositivi.
- Segreto per la firma: serve a verificare che le richieste arrivino davvero dal tuo dispositivo. Si copia dal menu ⋮; "Rigenera" lo cambia.
- Intestazione aggiuntiva (facoltativa): per i servizi che chiedono un token fisso, per esempio
Authorization: Bearer …. - Invia prova: manda una richiesta di tipo
testper controllare indirizzo e firma.
La configurazione vale per il dispositivo su cui la fai: con più casse va ripetuta su ognuna.
Quando arrivano le stampe
- Con l'invio continuo: subito dopo ogni stampa e quando l'app viene aperta.
- Sempre: con Invia ora.
- Le stampe partono una alla volta, in ordine. Se il tuo server non risponde o risponde con un errore, quella stampa e le successive restano in coda e si riprova alla stampa successiva, all'apertura dell'app o con Invia ora.
- Con l'app chiusa non parte niente.
- La stessa stampa può arrivare più di una volta (per esempio se la risposta del server si perde): riconoscila dal campo
id.
In Integrazioni trovi, giorno per giorno, i biglietti stampati e quanti sono stati inviati, sono in coda o hanno dato errore. Una stampa che il tuo server rifiuta sempre si può scartare.
La richiesta
POST con corpo JSON, una richiesta per stampa:
{
"id": "0b6c2f0e-6a1d-4c39-9d0e-0a4f1f1d2b7c",
"type": "print.completed",
"version": 1,
"createdAt": "2026-11-09T20:15:03.120+01:00",
"device": { "id": "d7a1…", "name": "Cassa 1", "counterPrefix": "A" },
"data": {
"printId": "0b6c2f0e-6a1d-4c39-9d0e-0a4f1f1d2b7c",
"reprint": false,
"reprintOf": null,
"template": { "id": "3f2c…", "name": "Biglietto ingresso" },
"fields": [
{ "key": "tipo", "label": "Tipo", "type": "select", "value": "Intero", "text": "Intero" },
{ "key": "prezzo", "label": "Prezzo", "type": "price", "value": 5, "text": "5,00 €" }
],
"copies": [
{ "id": "0b6c…-1", "n": 1, "counters": { "numero": { "value": 12, "text": "A-0012" } } },
{ "id": "0b6c…-2", "n": 2, "counters": { "numero": { "value": 13, "text": "A-0013" } } }
]
}
}fields: i campi del modulo, uguali per tutte le copie.valueè il dato grezzo (numeri come numeri, date in ISO 8601:2026-11-09per una data, con il fuso orario per data e ora),textè com'è stampato.typeè uno tratext,number,price,select,date,datetime.copies: un elemento per copia stampata, con unidproprio e i numeri dei campi contatore (con il prefisso del dispositivo, se c'è). Per una riga per biglietto, usa un elemento dicopiesper riga.- Ristampe:
reprint: trueereprintOfcon l'id della stampa originale; le copie hanno gli stessi numeri dell'originale. Non sono biglietti nuovi: escludile dai totali se serve. - Arrivano solo le copie uscite davvero dalla stampante.
type: "test": la richiesta di Invia prova, con lo stesso formato e dati di esempio.
Il tuo server deve rispondere con un codice 2xx (il corpo della risposta non viene letto) entro 15 secondi. Un 302 o 303 dopo la richiesta viene seguito con un GET senza dati (come fa Google Apps Script); 301, 307 e 308 sono errori: aggiorna l'indirizzo.
Intestazioni e firma
Content-Type: application/json; charset=utf-8
User-Agent: iiziticket/1.0.0
X-Iiziticket-Event: print.completed
X-Iiziticket-Delivery: <id>
X-Iiziticket-Timestamp: 1794251703
X-Iiziticket-Signature: sha256=<hex>La firma è l'HMAC-SHA256, in esadecimale, di timestamp + "." + corpo, con il segreto come chiave. Per verificarla:
- leggi il corpo così com'è, prima di convertirlo in JSON;
- calcola la firma e confrontala con
X-Iiziticket-Signaturecon un confronto a tempo costante; - rifiuta le richieste con un
X-Iiziticket-Timestamppiù vecchio di qualche minuto.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/iiziticket', express.raw({ type: 'application/json' }), (req, res) => {
const timestamp = req.get('X-Iiziticket-Timestamp');
const expected = 'sha256=' + crypto
.createHmac('sha256', process.env.IIZITICKET_SECRET)
.update(`${timestamp}.${req.body}`)
.digest('hex');
const signature = req.get('X-Iiziticket-Signature') ?? '';
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) < 300;
if (!fresh || signature.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body);
// event.data.copies: one item per printed ticket
res.sendStatus(204);
});$body = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_IIZITICKET_TIMESTAMP'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, getenv('IIZITICKET_SECRET'));
$signature = $_SERVER['HTTP_X_IIZITICKET_SIGNATURE'] ?? '';
if (abs(time() - (int) $timestamp) > 300 || !hash_equals($expected, $signature)) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
http_response_code(204);import hashlib, hmac, os, time
from flask import Flask, request
app = Flask(__name__)
@app.post("/iiziticket")
def iiziticket():
body = request.get_data()
timestamp = request.headers.get("X-Iiziticket-Timestamp", "")
expected = "sha256=" + hmac.new(
os.environ["IIZITICKET_SECRET"].encode(),
f"{timestamp}.".encode() + body,
hashlib.sha256,
).hexdigest()
signature = request.headers.get("X-Iiziticket-Signature", "")
if abs(time.time() - int(timestamp or 0)) > 300 or not hmac.compare_digest(expected, signature):
return "", 401
event = request.get_json()
return "", 204Servizi come Zapier o Make non verificano la firma: in quel caso usa un indirizzo difficile da indovinare (quello che ti danno loro) o l'intestazione aggiuntiva, se il servizio la supporta.
Esempio: un foglio Google
- Crea un foglio con le colonne che ti servono (es. data, cassa, template, tipo, prezzo, numero, ristampa).
- Estensioni → Apps Script, incolla:
function doPost(e) {
const event = JSON.parse(e.postData.contents);
if (event.type !== 'print.completed') return ContentService.createTextOutput('ok');
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
const field = (key) => (event.data.fields.find((f) => f.key === key) || {}).value;
for (const copy of event.data.copies) {
const counter = Object.values(copy.counters)[0];
sheet.appendRow([
event.createdAt, event.device.name, event.data.template.name,
field('tipo'), field('prezzo'), counter ? counter.text : '', event.data.reprint,
]);
}
return ContentService.createTextOutput('ok');
}- Esegui il deployment → Nuovo deployment → App web, accesso "Chiunque", e incolla l'URL in iiziticket.
Apps Script non legge le intestazioni, quindi non può verificare la firma: tieni privato l'URL.
Privacy
Le stampe partono dal tuo dispositivo verso l'indirizzo che indichi tu: iiziticket non le riceve e non le conserva. Di quei dati, e di dove li mandi, sei responsabile tu. Il resto è nell'Informativa privacy.