Przejdź do głównej zawartości

RODO i ochrona danych

Privacy by Design

qr3.app został zaprojektowany od podstaw z myślą o zgodności z RODO:

  • Pseudonimizacja IP — adresy IP są natychmiast konwertowane w Edge Worker na pseudonimy HMAC-SHA-256 dnia UTC. Oryginalny adres IP nigdy nie jest przechowywany; pseudonim pozostaje daną osobową.
  • Minimalizacja danych — gromadzone są wyłącznie dane niezbędne do działania usługi.

Privacy-API

Podsumowanie danych (art. 15 RODO)

GET /v1/account/privacy — Zwraca podsumowanie wszystkich przechowywanych danych.

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

Response:

{
"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 zlicza zapisane wpisy. scan_records to skanowania kodów QR, dpp_scan_records to zapytania do paszportów produktów przez publiczny resolver DPP. Oba zawierają pseudonim IP.

dashboard_activity_records i dashboard_document_activity_records zliczają dni, w których członek otworzył dashboard (dwie tabele), api_operation_records dzienne liczniki żądań według klucza API, operacji i wyniku, a webhook_delivery_records próby dostarczenia webhooków, w tym próby usuniętych webhooków. Żadna z nich nie przechowuje adresu IP ani User-Agent; log dostarczeń przechowuje jednak payload zdarzenia i pierwszy kilobajt (1 KB) odpowiedzi odbiorcy. Wszystkie cztery są usuwane po 90 dniach UTC (usage_retention, webhook_delivery_retention).

account wskazuje osobę stojącą za żądaniem — w dashboardzie zalogowaną osobę, w przeciwnym razie osobę, która utworzyła klucz — z adresem e-mail, imieniem i nazwiskiem, językiem i datą utworzenia; w przypadku Publishable Key wartość wynosi null. Pozostałe liczniki obejmują każdą dalszą tabelę z danymi konta: paszporty produktów, dokumenty DPP, pliki, identyfikatory GS1, komentarze, wpisy audytu, uruchomienia importu, członków workspace i powiadomienia o limicie organizacji (scan_quota_alerts). Wiersze usunięte logicznie są liczone, ponieważ nadal są przechowywane. erasure, backup_retention i billing_records opisują, co usuwa DELETE /v1/account, a co pozostaje przez pewien czas; pii_in_export ma wartość true, ponieważ eksport wskazuje osobę (e-mail, imię i nazwisko) — pseudonimy IP nigdy nie opuszczają bazy danych.


Eksport danych (art. 20 RODO — przenoszenie danych)

GET /v1/account/export — Pobiera wszystkie dane jako plik JSON.

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

Eksport zawiera:

  • Wszystkie kody QR (w tym usunięte)
  • Zagregowane statystyki skanowania (brak surowych skrótów IP)
  • Zagregowane zapytania DPP dla każdego paszportu produktu (dpp_scan_aggregates, również bez skrótów IP)
  • Statystyki użycia jako agregaty (dashboard_activity_aggregates, api_operation_aggregates): aktywne dni i liczba aktywnych użytkowników według kanału dashboardu, liczniki żądań według operacji i wyniku — bez identyfikatorów użytkowników i kluczy
  • Próby dostarczenia webhooków według webhooka i typu zdarzenia (webhook_delivery_aggregates), bez payloadu i odpowiedzi
  • Osobę stojącą za żądaniem (account), organizację (organization; adres rozliczeniowy i do powiadomień tylko dla administratorów organizacji) i workspace (workspace)
  • Paszporty produktów, metadane dokumentów DPP i plików (nie same pliki), identyfikatory GS1, wnioski o ich weryfikację i uruchomienia importu (dpp_passports, dpp_documents, files, gs1_identifiers, gs1_verification_requests, dpp_import_jobs)
  • Komentarze bez pól autora (code_comments), wpisy audytu jako agregaty (audit_log_aggregates) i członków według roli (member_aggregates)
  • Klucze API bez ID i skrótu (api_keys), webhooki bez URL i sekretu (webhooks) oraz powiadomienia o limicie organizacji (scan_quota_alerts)
  • Znaczniki czasu utworzenia i modyfikacji

Publishable Keys (qr3_pk_) otrzymują 403: plik zawiera adresy docelowe i notatki wszystkich kodów.


Usuwanie konta (art. 17 RODO — prawo do bycia zapomnianym)

DELETE /v1/account — Usuwa konto osoby stojącej za żądaniem, natychmiast i nieodwracalnie: w dashboardzie zalogowanej osoby, w przeciwnym razie osoby, która utworzyła klucz. Wniosek o usunięcie wysłany e-mailem prowadzi do tego samego usunięcia: operator usuwa logowanie w Clerk, a następnie qr3 usuwa konto.

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

Co się dzieje:

  1. Organizacje należące do osoby są usuwane wraz ze wszystkimi workspace, kodami, paszportami produktów, plikami, statystykami i logami, łącznie z ich plikami w magazynie. Jeśli taka organizacja ma innego aktywnego administratora organizacji, zamiast tego przejmuje ją ten z najdłuższym stażem — wraz z danymi i subskrypcją.
  2. Subskrypcja: trwająca subskrypcja kończy się natychmiast, bez proporcjonalnego zwrotu środków i bez faktury końcowej; następnie usuwany jest klient w Stripe. Faktury pozostają w Stripe przez ustawowy okres przechowywania (7 lat, § 132 BAO).
  3. Zespoły: członkostwa osoby w innych organizacjach kończą się; te zespoły zachowują swoje dane. Komentarze osoby nie wskazują już autora, jej wpisy audytu tracą pseudonim IP i user agent, a klucze API, które tam utworzyła, zostają unieważnione.
  4. Osoba: logowanie (Clerk) i rekord użytkownika zostają usunięte.

Odpowiedź to 200 ze status: "completed" i licznikami organizations_deleted, organizations_transferred, memberships_removed i login_deleted. W przypadku bardzo dużych organizacji jedno żądanie nie wystarcza: odpowiedź to wtedy 202 ze status: "in_progress", a usuwanie kończy się automatycznie w ciągu kilku godzin.

Wymaga Secret Key lub Test Key z prawem zapisu albo sesji w dashboardzie (dowolna rola, ponieważ chodzi o własne konto). Publishable Keys i klucze tylko do odczytu otrzymują 403, klucz bez osoby za nim (created_by = 'system') otrzymuje 409. 503 oznacza, że wdrożeniu brakuje sekretu potrzebnego do usunięcia; nic nie zostało usunięte.


Zarządzanie zgodami

GET /v1/account/privacy/consents — Pobieranie aktualnych zgód.

POST /v1/account/privacy/consents — Aktualizacja zgód.

Okno terminala
# Pobranie bieżących zgód
curl https://qr3.app/v1/account/privacy/consents \
-H "Authorization: Bearer qr3_sk_..."
# Wyłączenie e-maili marketingowych
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 }'

Pola zgód:

PoleDomyślnieOpis
marketing_emailsfalseNewsletter i e-maile marketingowe
analyticstrueZagregowane statystyki użytkowania
product_updatestrueNowości produktowe i dzienniki zmian (changelogi)

Zmiana zgód wymaga uprawnień do zapisu: rola viewer i Publishable Keys otrzymują 403.


Techniczna implementacja RODO

Pseudonimizacja IP (art. 25 RODO)

Żądanie HTTP → Cloudflare Edge Worker
↓
Nagłówek CF-Connecting-IP → HMAC-SHA-256(IP_HASH_SALT, cel, dzień UTC, IP)
↓
ip_hash (dzienny pseudonim) → baza danych D1
↓
Oryginalny adres IP nigdy nie jest przechowywany

IP_HASH_SALT jest długotrwałym głównym sekretem; cel i dzień UTC automatycznie wyprowadzają odrębne dzienne pseudonimy. Pseudonimy z różnych dni UTC nie są bezpośrednio takie same, lecz posiadacz sekretu może ponownie obliczyć znanego kandydata IP dla konkretnego dnia. Nie stosuje się dziennej soli w KV ani ręcznej codziennej rotacji.

Retencja danych (automatyczna)

PlanRetencja
Wszystkie plany90 dni

Statystyki użycia (aktywność w dashboardzie, liczniki użycia API) i logi dostarczeń webhooków są również przechowywane przez 90 dni UTC: bieżący dzień UTC i 89 poprzednich dni. To samo codzienne zadanie cron usuwa starsze wiersze (purgeOldDashboardActivity, purgeOldApiOperationUsage, purgeOldWebhookDeliveries).

Umowa DPA z podprocesorami

Cloudflare, Inc. przetwarza dane jako podmiot przetwarzający. Umowa DPA jest dostępna pod adresem cloudflare.com/cloudflare-customer-dpa.

Cloudflare przetwarza dane ze wskazaniem lokalizacji na region UE (m.in. Frankfurt); umownie zagwarantowana jurysdykcja UE nie jest obecnie wykupiona. Ponieważ spółka Cloudflare, Inc. ma siedzibę w USA, podstawą transferu są Standardowe Klauzule Umowne (SCC) zgodnie z art. 46 RODO.


Kontakt