Gå til indhold

GDPR & databeskyttelse

Privacy by Design

qr3.app er bygget til at være GDPR-kompatibel helt fra bunden:

  • IP-pseudonymisering — IP-adresser omdannes straks til HMAC-SHA-256 UTC-dagspseudonymer i Edge-workeren. Den oprindelige IP-adresse gemmes aldrig; pseudonymet er fortsat en personoplysning.
  • Dataminimering — Der indsamles kun de data, der er nødvendige for driften.

Privacy-API

Dataoversigt (Art. 15 GDPR)

GET /v1/account/privacy — Returnerer en oversigt over alle gemte data.

Terminal window
curl https://qr3.app/v1/account/privacy \
-H "Authorization: Bearer qr3_sk_..."

Svar:

{
"data": {
"workspace_id": "ws_xxx",
"organization_id": "org_xxx",
"account": {
"email": "[email protected]",
"name": "Alex Example",
"locale": "en",
"created_at": "2026-01-15T09:00:00.000Z"
},
"stored_data": {
"qr_codes": 42,
"scan_records": 15000,
"dpp_scan_records": 3200,
"dashboard_activity_records": 0,
"dashboard_document_activity_records": 36,
"api_operation_records": 180,
"webhook_delivery_records": 640,
"api_keys": 2,
"webhooks": 1,
"dpp_passports": 12,
"dpp_documents": 30,
"files": 4,
"gs1_identifiers": 2,
"gs1_verification_requests": 1,
"code_comments": 5,
"audit_log_entries": 0,
"dpp_import_jobs": 1,
"workspace_members": 3,
"scan_quota_alerts": 1
},
"gdpr_rights": {
"access": "GET /v1/account/privacy",
"export": "GET /v1/account/export",
"erasure": "DELETE /v1/account",
"consents": "GET /v1/account/privacy/consents"
},
"data_processing": {
"ip_storage": "Purpose-separated HMAC-SHA-256 UTC-day IP pseudonym — pseudonymous personal data",
"scan_retention": "90 days by default",
"usage_retention": "90 UTC days",
"webhook_delivery_retention": "90 UTC days",
"pii_in_export": true,
"erasure": "DELETE /v1/account erases the person's account: organizations they own with all workspaces and data (unless another org admin takes them over), their memberships in other organizations, their login and their Stripe customer. A running subscription ends immediately, without refund.",
"backup_retention": "Erased rows stay restorable from the database backups (Cloudflare D1 Time Travel) for up to 30 days, then they are gone.",
"billing_records": "Invoices stay with Stripe for the statutory retention period (7 years, § 132 BAO)."
}
},
"meta": { "request_id": "req_xxx" }
}

stored_data tæller de gemte poster. scan_records er scanninger af QR-koder, dpp_scan_records er forespørgsler til produktpas via den offentlige DPP-resolver. Begge indeholder IP-pseudonymet.

dashboard_activity_records og dashboard_document_activity_records tæller de dage, hvor et medlem har åbnet dashboardet (to tabeller), api_operation_records de daglige anmodningstællere pr. API-nøgle, operation og resultat, og webhook_delivery_records leveringsforsøgene for webhooks, også for slettede webhooks. Ingen af dem gemmer en IP-adresse eller en User-Agent; leveringsloggen gemmer dog hændelsens payload og det første kilobyte (1 KB) af modtagerens svar. Alle fire slettes efter 90 UTC-dage (usage_retention, webhook_delivery_retention).

account angiver personen bag anmodningen — i dashboardet den indloggede person, ellers den person, der oprettede nøglen — med e-mail, navn, sprog og oprettelsesdato; for en Publishable Key er værdien null. De øvrige tællere dækker alle andre tabeller med kontoens data: produktpas, DPP-dokumenter, filer, GS1-identifikatorer, kommentarer, audit-poster, importkørsler, workspace-medlemmer og organisationens kvotemeddelelser (scan_quota_alerts). Blødt slettede rækker tæller med, fordi de stadig er gemt. erasure, backup_retention og billing_records beskriver, hvad DELETE /v1/account sletter, og hvad der bliver en tid; pii_in_export er true, fordi eksporten angiver personen (e-mail, navn) — IP-pseudonymer forlader aldrig databasen.


Dataeksport (Art. 20 GDPR — dataportabilitet)

GET /v1/account/export — Downloader alle data som en JSON-fil.

Terminal window
curl https://qr3.app/v1/account/export \
-H "Authorization: Bearer qr3_sk_..." \
-o my-data.json

Eksporten indeholder:

  • Alle QR-koder (inkl. slettede)
  • Aggregerede scanningsstatistikker (ingen rå IP-hashes)
  • Aggregerede DPP-forespørgsler pr. produktpas (dpp_scan_aggregates, heller ingen IP-hashes)
  • Brugsstatistik som aggregater (dashboard_activity_aggregates, api_operation_aggregates): aktive dage og antal aktive brugere pr. dashboard-kanal, anmodningstællere pr. operation og resultat — uden bruger- og nøgle-ID’er
  • Webhook-leveringsforsøg pr. webhook og hændelsestype (webhook_delivery_aggregates), uden payload og svar
  • Personen bag anmodningen (account), organisationen (organization; fakturerings- og notifikationsadresse kun for org-administratorer) og workspacet (workspace)
  • Produktpas, metadata for DPP-dokumenter og filer (ikke selve filerne), GS1-identifikatorer, anmodninger om deres verificering og importkørsler (dpp_passports, dpp_documents, files, gs1_identifiers, gs1_verification_requests, dpp_import_jobs)
  • Kommentarer uden forfatterfelter (code_comments), audit-poster som aggregater (audit_log_aggregates) og medlemmer pr. rolle (member_aggregates)
  • API-nøgler uden id og hash (api_keys), webhooks uden URL og hemmelighed (webhooks) og organisationens kvotemeddelelser (scan_quota_alerts)
  • Oprettelses- og ændringstidsstempler

Publishable Keys (qr3_pk_) får 403: filen indeholder destinationsadresser og noter for alle koder.


Slet konto (Art. 17 GDPR — retten til at blive glemt)

DELETE /v1/account — Sletter kontoen for personen bag anmodningen, straks og uigenkaldeligt: i dashboardet den indloggede person, ellers den person, der oprettede nøglen. En sletteanmodning pr. e-mail fører til samme sletning: operatøren sletter login hos Clerk, og qr3 sletter derefter kontoen.

Terminal window
curl -X DELETE https://qr3.app/v1/account \
-H "Authorization: Bearer qr3_sk_..."

Hvad sker der:

  1. Organisationer, som personen ejer, slettes med alle workspaces, koder, produktpas, filer, statistikker og logs, inklusive deres filer i lageret. Har en sådan organisation en anden aktiv org-administrator, overtager den med længst anciennitet i stedet organisationen med data og abonnement.
  2. Abonnement: et løbende abonnement ophører straks, uden forholdsmæssig refusion og uden slutfaktura; derefter slettes Stripe-kunden. Fakturaer bliver hos Stripe i den lovpligtige opbevaringsperiode (7 år, § 132 BAO).
  3. Teams: personens medlemskaber i andre organisationer ophører; disse teams beholder deres data. Personens kommentarer angiver ikke længere en forfatter, personens audit-poster mister IP-pseudonym og user agent, og de API-nøgler, personen har oprettet der, tilbagekaldes.
  4. Personen: login (Clerk) og brugerposten slettes.

Svaret er 200 med status: "completed" og tællerne organizations_deleted, organizations_transferred, memberships_removed og login_deleted. For meget store organisationer rækker én anmodning ikke: svaret er så 202 med status: "in_progress", og sletningen afsluttes automatisk inden for timer.

Kræver en Secret eller Test Key med skriverettighed eller en dashboard-session (enhver rolle, da det er personens egen konto). Publishable Keys og skrivebeskyttede nøgler får 403, en nøgle uden en person bag (created_by = 'system') får 409. 503 betyder, at deploymentet mangler en hemmelighed, som sletningen kræver; intet er slettet.


Administrer samtykker

GET /v1/account/privacy/consents — Hent aktuelle samtykker.

POST /v1/account/privacy/consents — Opdater samtykker.

Terminal window
# Hent aktuelle samtykker
curl https://qr3.app/v1/account/privacy/consents \
-H "Authorization: Bearer qr3_sk_..."
# Deaktiver marketing-e-mails
curl -X POST https://qr3.app/v1/account/privacy/consents \
-H "Authorization: Bearer qr3_sk_..." \
-H "Content-Type: application/json" \
-d '{ "marketing_emails": false, "analytics": true, "product_updates": true }'

Samtykkefelter:

FeltStandardBeskrivelse
marketing_emailsfalseNyhedsbreve og reklame-e-mails
analyticstrueAggregerede brugsstatistikker
product_updatestrueProduktnyheder og ændringslogge

Ændring af samtykker kræver skriverettigheder: rollen viewer og Publishable Keys får 403.


Teknisk GDPR-implementering

IP-pseudonymisering (Art. 25 GDPR)

HTTP-anmodning → Cloudflare Edge Worker
↓
CF-Connecting-IP-header → HMAC-SHA-256(IP_HASH_SALT, formål, UTC-dag, IP)
↓
ip_hash (dagligt pseudonym) → D1-database
↓
Den oprindelige IP-adresse gemmes aldrig

IP_HASH_SALT er en langlivet hovedhemmelighed; formål og UTC-dag afleder automatisk adskilte daglige pseudonymer. Pseudonymer fra forskellige UTC-dage er ikke direkte ens, men en indehaver af hemmeligheden kan genberegne en kendt IP-kandidat for en bestemt dag. Der bruges hverken KV-dagssalt eller manuel daglig rotation.

Dataretention (automatiseret)

AbonnementDataretention
Alle abonnementer90 dage

Brugsstatistik (dashboard-aktivitet, API-brugstællere) og webhook-leveringslogs opbevares også i 90 UTC-dage: den aktuelle UTC-dag plus de 89 foregående dage. Det samme daglige cron-job sletter ældre rækker (purgeOldDashboardActivity, purgeOldApiOperationUsage, purgeOldWebhookDeliveries).

Underdatabehandler-DPA

Cloudflare, Inc. behandler data som databehandler. DPA’en er tilgængelig på cloudflare.com/cloudflare-customer-dpa.

Cloudflare behandler data med et placeringsforvalg (location hint) rettet mod EU-regionen (bl.a. Frankfurt); en kontraktligt garanteret EU-jurisdiktion er i øjeblikket ikke tilvalgt. Da Cloudflare, Inc. er baseret i USA, gælder standardkontraktbestemmelser (SCC’er) i henhold til art. 46 GDPR som overførselsgrundlag.


Kontakt