Aller au contenu

RGPD & Protection des données

Privacy by Design

qr3.app est conçu dès le départ pour être conforme au RGPD :

  • Pseudonymisation des IP — Les adresses IP sont immédiatement converties en pseudonymes HMAC-SHA-256 du jour UTC dans le Worker Edge. L’adresse IP d’origine n’est jamais stockée ; le pseudonyme reste une donnée personnelle.
  • Minimisation des données — Seules les données nécessaires au fonctionnement sont collectées.

API Privacy

Résumé des données (Art. 15 RGPD)

GET /v1/account/privacy — Retourne un aperçu de toutes les données stockées.

Fenêtre de terminal
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 compte les entrées enregistrées. scan_records correspond aux scans de QR Codes, dpp_scan_records aux consultations de passeports produit via le résolveur DPP public. Les deux contiennent le pseudonyme IP.

dashboard_activity_records et dashboard_document_activity_records comptent les jours où un membre a ouvert le tableau de bord (deux tables), api_operation_records les compteurs quotidiens de requêtes par clé API, opération et résultat, et webhook_delivery_records les tentatives de livraison de webhooks, y compris celles de webhooks supprimés. Aucune ne stocke d’adresse IP ni de User-Agent ; le journal de livraison stocke en revanche le payload de l’événement et le premier kilo-octet (1 Ko) de la réponse du destinataire. Les quatre sont supprimées après 90 jours UTC (usage_retention, webhook_delivery_retention).

account indique la personne à l’origine de la requête — dans le tableau de bord, la personne connectée, sinon la personne qui a créé la clé — avec son e-mail, son nom, sa langue et sa date de création ; pour une clé publiable, la valeur est null. Les autres compteurs couvrent toute autre table contenant des données du compte : passeports produits, documents DPP, fichiers, identifiants GS1, commentaires, entrées d’audit, exécutions d’import, membres du workspace et avis de quota de l’organisation (scan_quota_alerts). Les lignes supprimées de manière logique comptent, car elles sont encore stockées. erasure, backup_retention et billing_records décrivent ce que DELETE /v1/account supprime et ce qui subsiste un temps ; pii_in_export vaut true, car l’export indique la personne (e-mail, nom) — les pseudonymes IP ne quittent jamais la base de données.


Export des données (Art. 20 RGPD — Portabilité des données)

GET /v1/account/export — Télécharge toutes les données sous forme de fichier JSON.

Fenêtre de terminal
curl https://qr3.app/v1/account/export \
-H "Authorization: Bearer qr3_sk_..." \
-o my-data.json

L’export contient :

  • Tous les QR Codes (y compris ceux supprimés)
  • Des statistiques de scan agrégées (pas de hachages IP bruts)
  • Des consultations DPP agrégées par passeport produit (dpp_scan_aggregates, sans hachages IP non plus)
  • Des statistiques d’utilisation agrégées (dashboard_activity_aggregates, api_operation_aggregates) : jours actifs et nombre d’utilisateurs actifs par canal du tableau de bord, compteurs de requêtes par opération et résultat, sans ID d’utilisateur ni de clé
  • Des tentatives de livraison de webhooks agrégées par webhook et type d’événement (webhook_delivery_aggregates), sans payload ni réponse
  • La personne à l’origine de la requête (account), l’organisation (organization ; adresses de facturation et de notification uniquement pour les administrateurs d’organisation) et le workspace (workspace)
  • Les passeports produits, les métadonnées des documents DPP et des fichiers (pas les fichiers eux-mêmes), les identifiants GS1, les demandes de vérification de ceux-ci et les exécutions d’import (dpp_passports, dpp_documents, files, gs1_identifiers, gs1_verification_requests, dpp_import_jobs)
  • Les commentaires sans champs d’auteur (code_comments), les entrées d’audit agrégées (audit_log_aggregates) et les membres par rôle (member_aggregates)
  • Les clés API sans identifiant ni hachage (api_keys), les webhooks sans URL ni secret (webhooks) et les avis de quota de l’organisation (scan_quota_alerts)
  • Les horodatages de création et de modification

Les clés publiables (qr3_pk_) reçoivent 403 : le fichier contient les adresses cibles et les notes de tous les codes.


Supprimer le compte (Art. 17 RGPD — Droit à l’oubli)

DELETE /v1/account — Supprime le compte de la personne à l’origine de la requête, immédiatement et de manière irréversible : dans le tableau de bord, la personne connectée, sinon la personne qui a créé la clé. Une demande de suppression par e-mail entraîne la même suppression : l’exploitant supprime la connexion chez Clerk, puis qr3 supprime le compte.

Fenêtre de terminal
curl -X DELETE https://qr3.app/v1/account \
-H "Authorization: Bearer qr3_sk_..."

Ce qui se passe :

  1. Les organisations qui appartiennent à la personne sont supprimées avec tous leurs workspaces, codes, passeports produits, fichiers, statistiques et journaux, y compris leurs fichiers dans le stockage. Si une telle organisation compte un autre administrateur d’organisation actif, c’est le plus ancien qui la reprend à la place, avec ses données et son abonnement.
  2. Abonnement : un abonnement en cours prend fin immédiatement, sans remboursement au prorata et sans facture finale ; le client Stripe est ensuite supprimé. Les factures restent chez Stripe pendant la durée légale de conservation (7 ans, § 132 BAO).
  3. Équipes : les adhésions de la personne à d’autres organisations prennent fin ; ces équipes conservent leurs données. Les commentaires de la personne n’indiquent plus d’auteur, ses entrées d’audit perdent le pseudonyme IP et le user agent, et les clés API qu’elle y a créées sont révoquées.
  4. La personne : la connexion (Clerk) et l’enregistrement utilisateur sont supprimés.

La réponse est 200 avec status: "completed" et les compteurs organizations_deleted, organizations_transferred, memberships_removed et login_deleted. Pour les très grandes organisations, une requête ne suffit pas : la réponse est alors 202 avec status: "in_progress", et la suppression s’achève automatiquement en quelques heures.

Nécessite une Secret Key ou une Test Key avec droit d’écriture, ou une session du tableau de bord (tout rôle, puisqu’il s’agit de son propre compte). Les clés publiables et les clés en lecture seule reçoivent 403, une clé sans personne derrière elle (created_by = 'system') reçoit 409. 503 signifie qu’il manque au déploiement un secret nécessaire à la suppression ; rien n’a été supprimé.


Gérer les consentements

GET /v1/account/privacy/consents — Récupérer les consentements actuels.

POST /v1/account/privacy/consents — Mettre à jour les consentements.

Fenêtre de terminal
# Récupérer les consentements actuels
curl https://qr3.app/v1/account/privacy/consents \
-H "Authorization: Bearer qr3_sk_..."
# Désactiver les e-mails marketing
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 }'

Champs de consentement :

ChampPar défautDescription
marketing_emailsfalseNewsletter et e-mails promotionnels
analyticstrueStatistiques d’utilisation agrégées
product_updatestrueNouveautés produits et changelogs

La modification des consentements nécessite des droits d’écriture : le rôle viewer et les clés publiables reçoivent 403.


Implémentation technique du RGPD

Pseudonymisation des IP (Art. 25 RGPD)

Requête HTTP → Cloudflare Edge Worker
↓
En-tête CF-Connecting-IP → HMAC-SHA-256(IP_HASH_SALT, finalité, jour UTC, IP)
↓
ip_hash (pseudonyme quotidien) → base de données D1
↓
L'adresse IP d'origine n'est jamais stockée

IP_HASH_SALT est un secret maître de longue durée ; la finalité et le jour UTC dérivent automatiquement des pseudonymes quotidiens distincts. Les pseudonymes de jours UTC différents ne sont pas directement identiques, mais le détenteur du secret peut recalculer un candidat d’adresse IP connu pour un jour donné. Aucun sel quotidien KV ni rotation manuelle quotidienne n’est utilisé.

Rétention des données (automatisée)

PlanRétention
Tous les forfaits90 jours

Les statistiques d’utilisation (activité du tableau de bord, compteurs d’utilisation de l’API) et les journaux de livraison des webhooks sont eux aussi conservés 90 jours UTC : le jour UTC en cours et les 89 jours précédents. La même tâche cron quotidienne supprime les lignes plus anciennes (purgeOldDashboardActivity, purgeOldApiOperationUsage, purgeOldWebhookDeliveries).

DPA sous-traitant

Cloudflare, Inc. traite les données en tant que sous-traitant. L’accord de traitement des données (DPA) est disponible sur cloudflare.com/cloudflare-customer-dpa.

Cloudflare traite les données avec une indication de localisation pointant vers la région de l’UE (notamment Francfort) ; une juridiction européenne contractuellement garantie n’est actuellement pas souscrite. Comme Cloudflare, Inc. est établie aux États-Unis, les clauses contractuelles types (CCT) conformément à l’Art. 46 RGPD s’appliquent comme base de transfert.


Contact