Salta ai contenuti

Tracciamento delle scansioni e Analytics

Panoramica

Ogni scansione di un codice QR dinamico genera un record di scansione (Scan-Record) nel tuo workspace. Il rilevamento avviene interamente sull’edge di Cloudflare — senza servizi di tracciamento esterni, senza cookie, senza che l’IP originale raggiunga mai il tuo database.

Tre modi per consumare i dati di scansione:

API di aggregazione

GET /v1/codes/:id/scans — Serie temporali + Top paesi/dispositivi/OS per un singolo oggetto codice.

Webhook (in tempo reale)

Evento qr.scanned per scansione, firmato con HMAC-SHA256. Vedi Webhook.

Dashboard

Visualizzazione nella dashboard di qr3.app — nessun richiamo API necessario.


Quali dati vengono raccolti

Per ogni scansione viene creato un record nella tabella scans:

CampoOrigineEsempio
idUUID generato dal serverscn_d1f8…
code_idID del codice QR scansionatoqr_a1b2c3d4
workspace_idWorkspace del codicews_xxx
countryCloudflare cf.countryAT
regionCloudflare cf.regionVienna
cityCloudflare cf.cityWien
device_typeParsing dello User-Agentmobile, tablet, desktop
osParsing dello User-AgentiOS, Android, macOS, Windows
browserParsing dello User-AgentSafari, Chrome, Firefox
refererHeader HTTP Refererhttps://example.com/landing
languageHeader Accept-Language (prima lingua)de
redirected_toDestinazione effettiva del reindirizzamentohttps://example.com
ip_hashSHA-256(IP + salt giornaliero) — non reversibile8f3a…
scanned_atTimestamp ISO-86012026-05-12T14:32:11.000Z

Geolocalizzazione sull’Edge

Tutti i dati geografici (country, region, city) provengono dall’oggetto cf di Cloudflare e si basano sul database Geo-IP di Cloudflare. Non vengono richiesti servizi Geo-IP esterni — la risoluzione avviene nello stesso Worker che esegue anche il reindirizzamento.

Questo comporta tre conseguenze:

  1. Bassa latenza — nessun hop aggiuntivo, nessuna ricerca DNS su un provider esterno.
  2. Privacy — l’indirizzo IP non lascia Cloudflare e non viene mai trasmesso a terzi.
  3. Granularità limitatacity non è sempre disponibile (ad es. con VPN, operatori di telefonia mobile o piccole regioni). Aspettati valori null e tienine conto nelle tue dashboard.

Consultare le Analytics delle scansioni

GET /v1/codes/:id/scans

Fornisce analytics aggregate per un singolo codice QR in un periodo di tempo selezionabile.

Terminal window
curl "https://qr3.app/v1/codes/qr_a1b2c3d4/scans?days=30" \
-H "Authorization: Bearer qr3_sk_..."

Parametri di query:

ParametroTipoPredefinitoDescrizione
daysinteger30Periodo in giorni (1–365)

Risposta (HTTP 200):

{
"data": {
"code_id": "qr_a1b2c3d4",
"short_code": "r7f3Kx",
"total_scans": 1842,
"period_days": 30,
"period_scans": 367,
"scans_by_day": [
{ "date": "2026-04-13", "count": 12 },
{ "date": "2026-04-14", "count": 18 }
],
"top_countries": [
{ "value": "AT", "count": 142 },
{ "value": "DE", "count": 98 },
{ "value": "CH", "count": 41 }
],
"top_devices": [
{ "value": "mobile", "count": 281 },
{ "value": "desktop", "count": 72 },
{ "value": "tablet", "count": 14 }
],
"top_os": [
{ "value": "iOS", "count": 158 },
{ "value": "Android", "count": 123 },
{ "value": "macOS", "count": 48 }
]
},
"meta": { "request_id": "req_xyz123" }
}

Campi di risposta:

CampoDescrizione
total_scansTotale complessivo delle scansioni del codice (indipendente dalla finestra di giorni days)
period_scansSomma delle scansioni nel periodo di tempo selezionato
scans_by_dayBucket giornalieri, in ordine crescente per data, i giorni vuoti vengono omessi
top_countriesTop 8 paesi nel periodo di tempo, in ordine decrescente
top_devicesTop 5 tipi di dispositivi (mobile, tablet, desktop)
top_osTop 5 sistemi operativi

Aggregazione su più codici

I totali a livello di workspace (ad es. scansioni al mese su tutti i codici) sono disponibili tramite GET /v1/workspaces/:id nel campo scans_this_month. Per analisi incrociate tra più codici, consigliamo la dashboard o un’esportazione periodica.


Consumo in tempo reale tramite webhook

Se desideri elaborare gli eventi di scansione non appena si verificano — ad esempio per dashboard in tempo reale, tracciamento dei lead o integrazioni CRM — iscriviti all’evento qr.scanned:

Terminal window
curl -X POST https://qr3.app/v1/webhooks \
-H "Authorization: Bearer qr3_sk_..." \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/qr3",
"events": ["qr.scanned"],
"secret": "my-secret-key-min-16-chars"
}'

Payload:

{
"id": "evt_abc123xyz",
"type": "qr.scanned",
"created": "2026-05-12T14:32:11.000Z",
"data": {
"code_id": "qr_a1b2c3d4",
"short_code": "r7f3Kx",
"scan_id": "scn_d1f8...",
"country": "AT",
"device_type": "mobile",
"os": "iOS"
}
}

Documentazione completa su verifica delle firme, logica di riprovo (retry) e log di consegna in API → Webhook.


GDPR e protezione dei dati

I record di scansione non contengono indirizzi IP personali. L’IP originale viene sostituito nell’Edge Worker da una funzione di hash crittografica (SHA-256) con un salt a rotazione giornaliera. Ciò significa:

  • Nessuna re-identificazione anche conoscendo l’algoritmo
  • Nessuna correlazione tra giorni diversi dello stesso visitatore
  • L’IP originale non raggiunge mai il database D1

La retention (conservazione dei dati) dipende dal piano:

PianoConservazione
Free7 giorni
Pro90 giorni
Business / Agency1 anno
EnterprisePersonalizzato (SLA)

Un cron job giornaliero (purgeOldScans) rimuove automaticamente i record più vecchi — il valore total_scans sull’oggetto codice rimane invariato.


Best practice

  • Frequenza di polling: L’API di aggregazione è efficiente, ma non è pensata per frequenze al secondo. Per aggiornamenti in tempo reale, preferisci sempre i webhook e utilizza l’API solo per backfill o dashboard.
  • Caching: La risposta contiene serie temporali — con finestre temporali ampie (days=365), il payload può raggiungere diversi KB. Applica il caching lato client con un TTL breve (ad es. 60 s).
  • Limiti Top-N: top_countries, top_devices, top_os sono limitati lato server a 8 o 5 voci. Per analisi più approfondite, esporta i dati grezzi.
  • Tollerare i valori null: country, region, city, referer, language possono essere null. Gestisci questi casi nella tua analisi come “Sconosciuto”.