Aggregatie-API
GET /v1/codes/:id/scans — tijdreeksen + top-landen/-apparaten/-OS voor een afzonderlijk code-object.
Elke scan van een dynamischen QR-code genereert een scan-record in je workspace. De registratie vindt volledig plaats op de Cloudflare-edge — zonder externe trackingdiensten, zonder cookies en zonder dat het originele IP-adres ooit je database bereikt.
Drie manieren om scangegevens te consumeren:
Aggregatie-API
GET /v1/codes/:id/scans — tijdreeksen + top-landen/-apparaten/-OS voor een afzonderlijk code-object.
Webhooks (realtime)
qr.scanned-event per scan, HMAC-SHA256-gesigneerd. Zie Webhooks.
Dashboard
Visualisatie in het qr3.app-dashboard — geen API-call nodig.
Per scan wordt er een record in de tabel scans aangemaakt:
| Veld | Bron | Voorbeeld |
|---|---|---|
id | Door server gegenereerde UUID | scn_d1f8… |
code_id | Code-ID van de gescande QR-code | qr_a1b2c3d4 |
workspace_id | Workspace van de code | ws_xxx |
country | Cloudflare cf.country | AT |
region | Cloudflare cf.region | Vienna |
city | Cloudflare cf.city | Wien |
device_type | User-Agent-parsing | mobile, tablet, desktop |
os | User-Agent-parsing | iOS, Android, macOS, Windows |
browser | User-Agent-parsing | Safari, Chrome, Firefox |
referer | HTTP Referer-header | https://example.com/landing |
language | Accept-Language-header (eerste taal) | de |
redirected_to | Werkelijke redirect-bestemming | https://example.com |
ip_hash | SHA-256(IP + dagelijkse salt) — niet omkeerbaar | 8f3a… |
scanned_at | ISO-8601-timestamp | 2026-05-12T14:32:11.000Z |
Alle geogegevens (country, region, city) zijn afkomstig uit het cf-object van Cloudflare und zijn gebaseerd op de Cloudflare Geo-IP-database. Er worden geen externe Geo-IP-services aangeroepen — de herleiding vindt plaats in dezelfde worker die ook de redirect uitvoert.
Dit heeft drie gevolgen:
city is niet altijd beschikbaar (bijv. bij VPN’s, mobiele providers of kleine regio’s). Houd rekening met null-waarden in je dashboards.GET /v1/codes/:id/scansLevert geaggregeerde analytics voor een afzonderlijke QR-code over een selecteerbare periode.
curl "https://qr3.app/v1/codes/qr_a1b2c3d4/scans?days=30" \ -H "Authorization: Bearer qr3_sk_..."const analytics = await qr3.scans.get('qr_a1b2c3d4', { days: 30 });console.log(analytics.period_scans, analytics.top_countries);qr3 scans qr_a1b2c3d4 --days 30Queryparameters:
| Parameter | Type | Standaard | Beschrijving |
|---|---|---|---|
days | integer | 30 | Periode in dagen (1–365) |
Response (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" }}Responsevelden:
| Veld | Beschrijving |
|---|---|
total_scans | Totaal aantal scans van de code over de gehele levensduur (onafhankelijk van het days-venster) |
period_scans | Totaal aantal scans in de geselecteerde periode |
scans_by_day | Dagelijkse buckets, oplopend op datum, lege dagen worden weggelaten |
top_countries | Top 8 landen in de periode, aflopend |
top_devices | Top 5 apparaattypen (mobile, tablet, desktop) |
top_os | Top 5 besturingssystemen |
Workspace-brede totalen (bijv. scans per maand over alle codes) zijn beschikbaar via GET /v1/workspaces/:id in het veld scans_this_month. Voor analyses over meerdere codes raden we het dashboard of een periodieke export aan.
Als je scan-events wilt verwerken zodra ze plaatsvinden — bijvoorbeeld voor live dashboards, lead-tracking of CRM-integraties — abonneer je dan op het qr.scanned-event:
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" }}Volledige documentatie over handtekeningverificatie, retry-logica en delivery-logs in API → Webhooks.
Scan-records bevatten geen persoonsidentificerende IP-adressen. Het oorspronkelijke IP-adres wordt in de edge-worker door een cryptografische hashfunctie (SHA-256) met een dagelijks roterende salt vervangen. Dit betekent:
Retentie is afhankelijk van het abonnement:
| Abonnement | Bewaartermijn |
|---|---|
| Free | 7 dagen |
| Pro | 90 dagen |
| Business / Agency | 1 jaar |
| Enterprise | Custom (SLA) |
Een dagelijks uitgevoerde cronjob (purgeOldScans) verwijdert oudere records automatisch — total_scans op het code-object blijft hierdoor ongewijzigd.
days=365) kan de payload meerdere KB groot worden. Cache aan de clientzijde met een korte TTL (bijv. 60 s).top_countries, top_devices en top_os zijn aan de serverzijde beperkt tot respectievelijk 8 of 5 vermeldingen. Exporteer de ruwe gegevens voor diepgaandere analyses.null-waarden tolereren: country, region, city, referer en language kunnen null zijn. Behandel dit in je analyse als “Onbekend”.