iiziticket
How it worksWho it's forFeaturesPricingFAQ
Log inGet it free
  • ItalianoIT
  • EnglishEN

Receive prints on your server

Last updated: 9 November 2026

With the monthly or yearly subscription, iiziticket can send every print to a web address you choose: your management software, a script, Zapier, Make, n8n, Google Apps Script. You get one item per printed copy, with the form values and the counter numbers. Totals, summaries and anything else are up to your own services.

Prints are sent by the phone or tablet that prints them: the data never goes through iiziticket's servers.

In short

  • One JSON POST request per print, with one item per copy.
  • Signed with HMAC-SHA256: your server checks it comes from your device.
  • If the server does not answer, the print waits in a queue and is sent again; printing never waits for it.
  • Set it up in the app: Settings → Integrations.

Setup

  • Address (URL): where to receive prints. It must start with https://. For a server on the shop's network, http:// is allowed with local addresses (192.168.x.x, 10.x.x.x, 172.16–31.x.x, *.local).
  • Continuous sending: on, each print is sent right after it comes out; off, prints wait in the queue until you tap Send now.
  • Device name: sent with every print (e.g. "Till 1"), useful when printing from several devices.
  • Signing secret: lets you check that requests really come from your device. Copy it from the ⋮ menu; "Regenerate" changes it.
  • Extra header (optional): for services that want a fixed token, for example Authorization: Bearer ….
  • Send test: sends a test request to check the address and the signature.

The setup applies to the device you make it on: with several tills, repeat it on each one.

When prints arrive

  • With continuous sending: right after each print and when the app is opened.
  • Always: with Send now.
  • Prints are sent one at a time, in order. If your server does not answer or answers with an error, that print and the following ones wait in the queue and are tried again at the next print, when the app opens or with Send now.
  • Nothing is sent while the app is closed.
  • The same print can arrive more than once (for example if the server's answer gets lost): recognise it by its id.

In Integrations you can see, day by day, the tickets printed and how many were sent, are queued or failed. A print your server always refuses can be discarded.

The request

POST with a JSON body, one request per print:

JSON
{
  "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: the form fields, the same for every copy. value is the raw value (numbers as numbers, dates in ISO 8601: 2026-11-09 for a date, with the time zone for date and time), text is as printed. type is one of text, number, price, select, date, datetime.
  • copies: one item per printed copy, with its own id and the numbers of the counter fields (with the device prefix, if any). For one row per ticket, use one item of copies per row.
  • Reprints: reprint: true and reprintOf with the id of the original print; the copies have the same numbers as the original. They are not new tickets: leave them out of totals if needed.
  • Only the copies that actually came out of the printer are sent.
  • type: "test": the Send test request, with the same format and sample data.

Your server must answer with a 2xx code (the response body is not read) within 15 seconds. A 302 or 303 after the request is followed with a GET without data (as Google Apps Script does); 301, 307 and 308 are errors: update the address.

Headers and signature

HTTP
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>

The signature is the HMAC-SHA256, in hex, of timestamp + "." + body, keyed with the secret. To check it:

  1. read the body as is, before parsing it as JSON;
  2. compute the signature and compare it with X-Iiziticket-Signature in constant time;
  3. refuse requests whose X-Iiziticket-Timestamp is more than a few minutes old.
Node.js (Express)
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);
});
PHP
$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);
Python (Flask)
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 "", 204

Services such as Zapier or Make do not check the signature: in that case use an address that is hard to guess (the one they give you) or the extra header, if the service supports it.

Example: a Google Sheet

  1. Create a sheet with the columns you need (e.g. date, till, template, type, price, number, reprint).
  2. Extensions → Apps Script, paste:
Apps Script
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');
}
  1. Deploy → New deployment → Web app, access "Anyone", and paste the URL into iiziticket.

Apps Script cannot read headers, so it cannot check the signature: keep the URL private.

Privacy

Prints go from your device to the address you choose: iiziticket neither receives nor stores them. You are responsible for that data and for where you send it. Everything else is in the privacy policy.

© 2026 iiziticket · Tickets and receipts on your thermal printer
PrivacyTermsIntegrationsContact