Salta ai contenuti

Webhooks

Webhooks

Con i webhook ricevi notifiche in tempo reale quando si verificano eventi nel tuo workspace, ad esempio quando viene scansionato un codice QR.

Creare un webhook

Terminal window
POST /v1/webhooks
{
"url": "https://example.com/webhooks/qr3",
"events": ["qr.scanned", "qr.created"],
"secret": "my-secret-key-min-16-chars"
}

Risposta

{
"id": "wh_abc123",
"url": "https://example.com/webhooks/qr3",
"events": ["qr.scanned", "qr.created"],
"is_active": true,
"secret": "my-secret-key-min-16-chars",
"secret_hint": "my-s…",
"created_at": "2026-03-15T10:00:00.000Z"
}

Tipi di evento

EventoDescrizione
*Tutti gli eventi
qr.createdCodice QR creato
qr.updatedCodice QR aggiornato (URL, stato, tag)
qr.deletedCodice QR eliminato
qr.scannedCodice QR scansionato
qr.flaggedCodice QR contrassegnato come non sicuro
webhook.pingPing di test (tramite l’endpoint /ping)

Formato del payload

Tutti i payload dei webhook hanno questo formato:

{
"id": "evt_4f0c2a91d7e84b3c9a1f",
"type": "qr.scanned",
"created": "2026-03-15T10:00:00.000Z",
"data": {
"code_id": "qr_abc123",
"short_code": "r7f3Kx",
"scanned_at": "2026-03-15T10:00:00.000Z",
"country": "AT",
"device_type": "mobile",
"os": "iOS",
"browser": "Safari",
"language": "de",
"ab_variant": null,
"redirected_to": "https://example.com/landing"
}
}

Eventi codice: qr.created e qr.updated

In questi due eventi, data è il codice esattamente come viene restituito da GET /v1/codes/:id. Questo include appearance, ovvero i colori memorizzati sul codice (null rappresenta il nero o il bianco predefinito):

{
"id": "evt_9b2e41c07a5d4f18b3c6",
"type": "qr.updated",
"created": "2026-09-28T10:00:00.000Z",
"data": {
"id": "qr_abc123",
"short_code": "r7f3Kx",
"status": "live",
"appearance": { "foreground_color": "#1F4E79", "background_color": null },
"updated_at": "2026-09-28T10:00:00.000Z"
}
}

L’esempio è abbreviato. Tutti i campi sono disponibili nelle API dei codici.

Verifica della firma

qr3.app firma ogni richiesta webhook con HMAC-SHA256:

X-QR3-Signature: sha256=a1b2c3d4e5f6...
X-QR3-Event: qr.scanned
X-QR3-Delivery: wdl_abc123
X-QR3-Attempt: 1
User-Agent: qr3-webhooks/1.0

Implementare la verifica

import crypto from "crypto";
function verifySignature(
payload: string,
signature: string | undefined,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
const expectedBytes = Buffer.from(`sha256=${expected}`);
const receivedBytes = Buffer.from(signature ?? "");
return (
expectedBytes.length === receivedBytes.length &&
crypto.timingSafeEqual(expectedBytes, receivedBytes)
);
}
// In Express: verify the raw body — re-serialised JSON breaks the signature.
app.post(
"/webhooks/qr3",
express.raw({ type: "application/json" }),
(req, res) => {
const raw = req.body.toString("utf8");
const signature = req.headers["x-qr3-signature"] as string | undefined;
if (!verifySignature(raw, signature, process.env.QR3_WEBHOOK_SECRET!)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(raw);
console.log(event.type, event.data);
res.json({ received: true });
},
);

Logica di retry

In caso di errori (HTTP >= 300 o timeout), qr3.app riprova a inviare la notifica:

TentativoRitardo
1Immediatamente
21 minuto
35 minuti

Dopo 10 errori consecutivi, il webhook viene disattivato automaticamente.

Garanzia di consegna

qr3.app consegna almeno una volta, non esattamente una. Lo stesso evento può quindi raggiungere il tuo endpoint più di una volta: come nuovo tentativo dopo un errore o come ripetizione di un tentativo già inviato.

  • Deduplica con l’id del payload (evt_…). Appartiene all’evento e resta uguale per ogni tentativo e ogni ripetizione. X-QR3-Delivery identifica il singolo tentativo: una ripetizione di quel tentativo porta lo stesso valore, un nuovo tentativo un valore nuovo. Conferma con un 2xx un id che già conosci, non con un 4xx: un 4xx conta come tentativo fallito e, dopo 10 errori consecutivi, il webhook viene disattivato automaticamente.
  • Un tentativo ancora pending un’ora dopo la sua creazione (ad esempio perché il suo messaggio in coda è andato perso) viene impostato su failed da una pulizia giornaliera; status_code e response_time_ms restano null. Non è un errore del tuo endpoint e non conta tra i 10 errori consecutivi. Il log memorizza per questo il testo Delivery was not completed in time — closed by cleanup; l’endpoint dei log di consegna non lo restituisce. La chiusura non è definitiva: se il messaggio arriva comunque, viene consegnato e il suo risultato reale sostituisce quel testo.

Log di consegna

Terminal window
GET /v1/webhooks/:id/deliveries
{
"data": [
{
"id": "wdl_abc123",
"event_type": "qr.scanned",
"status": "success",
"status_code": 200,
"response_time_ms": 145,
"attempt": 1,
"created_at": "2026-03-15T10:00:00.000Z"
}
]
}

I tentativi di consegna vengono conservati per 90 giorni (il giorno UTC in corso e gli 89 precedenti) e poi eliminati automaticamente. Oltre ai campi mostrati, il log memorizza il payload dell’evento e il primo kilobyte (1 KB) della risposta dell’endpoint; questo endpoint non restituisce né l’uno né l’altro.

Ping di test

Testa immediatamente un webhook:

Terminal window
POST /v1/webhooks/:id/ping

Questo invia un evento webhook.ping all’URL del tuo endpoint.