Documentazione
In questa guida trovi le indicazioni per integrarti con le API di Pinbloom: creare una chiave, generare QR code, testare l'intero flusso, e ricevere le notifiche tramite webhook.
1. Chiavi API
Una chiave API autentica ogni richiesta che fai all'API di Pinbloom. Puoi creare due tipi di chiave, e ogni endpoint accetta solo uno dei due:
- Sandbox — pensata per lo sviluppo. Ogni endpoint simula i processi reali senza salvare nulla e senza consumare quote dal tuo piano.
- Live — genera QR code veri, ognuno conteggiato nella quota mensile del tuo piano.
Una chiave sandbox funziona solo sugli endpoint sandbox, una chiave live solo su quello di produzione — usare quella sbagliata dà un 401.
Creare una chiave
- Vai in Impostazioni → Sviluppatori.
- Apri il tab Sandbox o Live, a seconda di quale ti serve.
- Clicca Nuova key, dalle un nome che ti ricordi dove la usi (es. "Server produzione", "Sviluppo locale"), poi Crea.
- La chiave compare nella tabella, parzialmente nascosta — clicca l'icona a forma di occhio per rivelarla, o quella di copia per copiarla direttamente negli appunti.
Il tuo piano permette un numero limitato di chiavi per ciascun tipo — il conteggio sopra la tabella ("x/y usate") mostra quante te ne restano; elimina quelle che non usi più per liberare uno slot.
Autenticare le richieste
Manda la chiave come bearer token su ogni richiesta:
Authorization: Bearer qrv_live_xxxxxxxxxxxxxxxx
Le chiavi sandbox iniziano con qrv_sandbox_, quelle live con qrv_live_ — coerentemente con l'endpoint che stai chiamando.
2. Generare un QR code
POST /api/qr/generate — richiede una chiave live. Salva il QR code, ne genera l'immagine, e consuma 1 dalla tua quota mensile. Ogni campo nel corpo della richiesta è opzionale:
| Campo | Tipo | A cosa serve |
|---|---|---|
| tag | string | Una tua etichetta, restituita così com'è. Max 255 caratteri. |
| metadata | object | Libero. Restituito nei webhook e nella risposta di validazione del QR — mai mostrato all'operatore che lo scansiona. Max 20KB serializzato. |
| displayFields | array di {label, value} | Informazioni leggibili mostrate all'operatore quando scansiona il QR (es. numero posto, id ordine) — non incluse nei webhook. Max 20 elementi, 20KB serializzato. |
| starts_at | date-time | Da quando il QR è valido: scansionato prima, risponde not_started e resta non usato. Omettilo per un QR valido da subito. Se indichi anche expires_at, deve venire prima. |
| expires_at | date-time | Deve essere nel futuro. Omettilo per un QR code che non scade mai. |
| format | qrcode | barcode |
Che cosa generare. Omettendolo ottieni un qrcode. Vedi sotto cosa cambia con barcode. |
curl -X POST https://pinbloom.eu/api/qr/generate \
-H "Authorization: Bearer qrv_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"tag": "promo-2026",
"displayFields": [{ "label": "Posti", "value": "4" }],
"expires_at": "2026-12-31T23:59:59Z"
}'
Risposta (201):
{
"uuid": "019f2b3c-4a11-7c3e-9c2a-1b8e6f0a2d4c",
"tag": "promo-2026",
"expiresAt": "2026-12-31T23:59:59+00:00",
"format": "qrcode",
"shortCode": null,
"qrImage": "PHN2ZyB4bWxucz0i...",
"imageUrl": "https://pinbloom.eu/c/k7Qm2...png"
}
qrImage è il QR code stesso — un'immagine SVG, codificata in base64. Decodificala per ottenere il file immagine vero e proprio.
imageUrl è l'indirizzo pubblico di un PNG del codice: mettilo in un <img> delle email che mandi ai tuoi clienti o nella pagina dell'ordine. I programmi di posta non mostrano gli SVG, e Gmail blocca le immagini in base64 dentro l'email. L'indirizzo non richiede la chiave API, non scade e funziona anche dopo l'uso o la revoca del codice. Alcuni programmi di posta, per esempio Outlook desktop, bloccano le immagini esterne finché il lettore non le permette: metti nell'email anche il codice in testo, o un link al biglietto.
Nessun QR code rimasto sul tuo piano? Ricevi un 422 invece, e non viene creato né dedotto nulla.
Abbonamento terminato? Ricevi un 403: dopo la scadenza non si generano nuovi codici, né qui né in sandbox, anche con crediti extra. I codici già creati restano validi per 6 mesi.
Codici a barre
Con "format": "barcode" il campo qrImage contiene un Code128 invece di un QR. Resta un SVG codificato in base64.
Per il resto non cambia niente. tag, metadata e displayFields si comportano allo stesso modo, i webhook arrivano identici, e all'ingresso la validazione funziona senza che tu debba fare nulla di diverso.
L'unica differenza nella risposta è shortCode: il numero di 14 cifre che il codice a barre contiene, nel caso ti serva stamparlo in chiaro sotto all'immagine. Con un QR è null.
Più codici in una chiamata
POST /api/qr/generate-batch — richiede una chiave live. Il corpo ha un solo campo, codes: un array in cui ogni elemento ha gli stessi campi di /api/qr/generate. Così ogni biglietto di un ordine può avere il suo tag, i suoi displayFields e le sue date. La risposta (201) è { "codes": [ … ] }, nello stesso ordine della richiesta, con gli stessi campi di /api/qr/generate.
422 e non viene creato nessun codice. Per un ordine più grande, dividilo in più chiamate.
Tutto o niente. Se la quota non basta per tutti i codici, o un elemento non è valido, non viene creato nessun codice e ricevi un 422. L'errore di un elemento indica la sua posizione, per esempio codes.2.expires_at. Un ordine non resta mai con metà dei biglietti.
curl -X POST https://pinbloom.eu/api/qr/generate-batch \
-H "Authorization: Bearer qrv_live_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1042" \
-d '{
"codes": [
{ "tag": "order-1042", "displayFields": [{ "label": "Posto", "value": "A1" }] },
{ "tag": "order-1042", "displayFields": [{ "label": "Posto", "value": "A2" }] }
]
}'
Ripetere una chiamata senza creare doppioni
/api/qr/generate o a /api/qr/generate-batch va in timeout, non sai se i codici sono stati creati. Manda l'header Idempotency-Key con una stringa tua, per esempio l'ID dell'ordine, e ripeti la chiamata senza paura:- stessa chiave e stessa richiesta: ricevi la stessa risposta della prima volta, con l'header
Idempotent-Replayed: true, e non viene creato niente; - stessa chiave con una richiesta diversa:
422; - stessa chiave mentre la prima chiamata è ancora in corso:
409. Riprova tra poco.
3. Gestire i codici
Tre endpoint, con una chiave live, per i codici creati via API dall'account della chiave, con qualunque sua chiave live. I codici delle raccolte sono a parte: con questi endpoint rispondono 404, come un codice che non esiste.
Leggere lo stato
GET /api/qr/{uuid} — la consultazione funziona sempre, anche dopo la fine dell'abbonamento. Risposta (200):
{
"uuid": "019f2b3c-4a11-7c3e-9c2a-1b8e6f0a2d4c",
"status": "unused",
"tag": "promo-2026",
"format": "qrcode",
"shortCode": null,
"usedAt": null,
"revokedAt": null,
"startsAt": null,
"expiresAt": "2026-12-31T23:59:59+00:00"
}
| status | Significato |
|---|---|
unused |
Non usato: alla scansione risulta valido. |
used |
Già usato. usedAt dice quando. |
revoked |
Revocato. revokedAt dice quando. |
expired |
Non usato, ma oltre expiresAt. |
not_started |
Non usato, ma prima di startsAt. |
Revocare e ripristinare
curl -X POST https://pinbloom.eu/api/qr/019f2b3c-4a11-7c3e-9c2a-1b8e6f0a2d4c/revoke \ -H "Authorization: Bearer qrv_live_xxxxxxxxxxxxxxxx"
POST /api/qr/{uuid}/revoke revoca un codice non usato: da quel momento non passa più la validazione. POST /api/qr/{uuid}/restore riporta un codice revocato a non usato. Tutti e due rispondono (200) con lo stato del codice, come la lettura.
Revocare un codice già revocato, o ripristinare un codice non revocato, non cambia niente e risponde 200: puoi ripetere la chiamata senza problemi. Un codice usato non si può né revocare né ripristinare: 409.
Abbonamento terminato? Nei 6 mesi in cui i codici restano validi puoi ancora revocare. Il ripristino richiede un abbonamento attivo: 403.
4. Testare senza andare in produzione
Generare un codice di prova
POST /api/sandbox/qr/generate richiede una chiave sandbox e non consuma quota. Non legge il corpo della richiesta: crea sempre un QR code, mai un codice a barre, e risponde solo con uuid e qrImage. Per provare i campi, il formato e la risposta completa dell'endpoint live usa POST /api/sandbox/qr/generate-batch, anche con un solo codice.
curl -X POST https://pinbloom.eu/api/sandbox/qr/generate \ -H "Authorization: Bearer qrv_sandbox_xxxxxxxxxxxxxxxx"
Un QR code generato così esiste solo nella risposta che ricevi. Non può essere "scansionato" dall'app operatore.
POST /api/sandbox/qr/generate-batch fa lo stesso per più codici: stesso corpo, stesso limite di 100 codici, stessi campi nella risposta. Non salva niente, quindi imageUrl è null.
Simulare una scansione
POST /api/sandbox/simulate — per testare in sandbox i dati ricevuti tramite webhook devi usare questo endpoint:
| Campo | Cosa mandare |
|---|---|
| qr_code | Una stringa qualsiasi, a tua scelta — restituita così com'è, non deve essere un UUID vero. |
| simulate | Uno tra: valid, already_used, expired, revoked, not_started, not_found. |
curl -X POST https://pinbloom.eu/api/sandbox/simulate \
-H "Authorization: Bearer qrv_sandbox_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "qr_code": "abc123", "simulate": "valid" }'
Ogni esito tranne not_found fa partire l'evento corrispondente (sezione 5) verso i webhook registrati su questa chiave sandbox e iscritti a quell'evento. La consegna è identica a quella di produzione: stessa firma, stessi cinque tentativi. È l'unico modo per collaudare un'integrazione webhook prima di andare live.
5. Webhook
Disponibile sui piani Pro e Business. Un webhook notifica un tuo server ogni volta che un QR cambia stato (solo QR generati tramite API, no eventi). Una stessa API key può avere più webhook (es. uno per lo staging, uno per un tunnel ngrok locale mentre sviluppi l'integrazione), e ognuno è iscritto in modo indipendente solo agli eventi che ti interessano.
Creare un webhook
- Vai in Impostazioni → Sviluppatori e apri il tab Sandbox o Live — a seconda di a quale chiave lo vuoi collegare.
- Nella riga di quella chiave, clicca sul badge Webhook ("Nessun webhook" o un conteggio) per aprire il pannello.
- Clicca Nuovo webhook.
- Dagli un nome, un URL pubblico
https://, e scegli gli eventi che deve ricevere — tutti sono selezionati di default. - Clicca Crea. Il suo secret di firma è mostrato subito lì sulla card: clicca l'icona a forma di occhio per rivelarlo, quella di copia per copiarlo.
Usa quel secret nel tuo progetto per verificare che una richiesta arrivi davvero da Pinbloom (vedi paragrafo "Verificare che una richiesta sia autentica").
| Evento | Quando scatta |
|---|---|
qr.used |
Il QR era valido ed è appena stato segnato come usato. |
qr.already_used |
Qualcuno ha scansionato un QR già usato. |
qr.revoked |
Qualcuno ha scansionato un QR che avevi revocato. |
qr.expired |
Qualcuno ha scansionato un QR oltre la sua scadenza. |
qr.not_started |
Qualcuno ha scansionato un QR prima del suo inizio di validità. |
Un webhook riceve solo gli eventi a cui è iscritto — li scegli quando lo crei, oppure li cambi in seguito dallo stesso pannello. Ogni evento ha la stessa struttura:
{
"event": "qr.used",
"data": {
"uuid": "019f...",
"tag": "promo-2026",
"metadata": { "campaign": "estate" },
"usedAt": "2026-08-10T14:30:00+00:00",
"revokedAt": null,
"startsAt": null,
"expiresAt": null,
"scannedAt": "2026-08-10T14:30:00+00:00",
"offline": false
}
}
scannedAt è il momento della scansione. offline è true quando l'app dell'operatore era senza rete: la notifica parte quando la scansione arriva al server, quindi anche molto dopo scannedAt.
Verificare che una richiesta sia autentica
Ogni richiesta porta un header X-Pinbloom-Signature: l'HMAC-SHA256 del corpo esatto della richiesta, calcolato con il secret proprio di quel webhook. Verificalo prima di fidarti di qualsiasi cosa nel payload:
$atteso = hash_hmac('sha256', $corpoRichiesta, $secretSalvato);
if (! hash_equals($atteso, $request->header('X-Pinbloom-Signature'))) {
abort(401); // non autentico, scarta
}
// verificato — puoi fidarti del contenuto
Lo stesso schema HMAC-SHA256 funziona in qualsiasi linguaggio — calcolalo sul corpo grezzo della richiesta e confronta con un controllo a tempo costante.
Requisiti e consegna
- L'URL deve essere un indirizzo pubblico
https://— nientelocalhost, niente rete privata. Per lo sviluppo locale, usa un tunnel come ngrok. - Se il tuo endpoint non risponde in tempo o dà errore, la consegna viene ritentata automaticamente — fino a 5 tentativi in circa 40 minuti, con ritardo crescente tra uno e l'altro — prima di essere abbandonata.
- Ogni webhook viene consegnato in modo indipendente: un endpoint lento o irraggiungibile influisce solo sui propri retry, mai sugli altri tuoi webhook.
- Dopo la scadenza dell'abbonamento i webhook continuano a partire per 6 mesi. Poi l'account viene sospeso: ogni scansione risponde «Servizio scaduto» e i webhook non partono più.