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.
curl https://qr3.app/v1/account/privacy \ -H "Authorization: Bearer qr3_sk_..."Svar:
{ "data": { "workspace_id": "ws_xxx", "organization_id": "org_xxx", "account": { "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.
curl https://qr3.app/v1/account/export \ -H "Authorization: Bearer qr3_sk_..." \ -o my-data.jsonEksporten 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.
curl -X DELETE https://qr3.app/v1/account \ -H "Authorization: Bearer qr3_sk_..."Hvad sker der:
- 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.
- 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).
- 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.
- 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.
# Hent aktuelle samtykkercurl https://qr3.app/v1/account/privacy/consents \ -H "Authorization: Bearer qr3_sk_..."
# Deaktiver marketing-e-mailscurl -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:
| Felt | Standard | Beskrivelse |
|---|---|---|
marketing_emails | false | Nyhedsbreve og reklame-e-mails |
analytics | true | Aggregerede brugsstatistikker |
product_updates | true | Produktnyheder 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 aldrigIP_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)
| Abonnement | Dataretention |
|---|---|
| Alle abonnementer | 90 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
- Databeskyttelsesrådgiver: [email protected]
- Databehandleraftale (DPA): [email protected] (efter anmodning)
- Privatlivspolitik: qr3.app/de/legal/datenschutz