Implement GDPR data subject rights at scale. Covers right to access, right to erasure, data portability, consent management, request processing pipelines, and the engineering patterns that make privacy compliance automated and auditable.
Data subject rights under the GDPR are not a legal checklist—they are engineering challenges. When a user requests access to their data, a deletion of their personal information, or a right to data portability, the system must respond accurately, efficiently, and in compliance with the law. This page details how to engineer these rights end-to-end, from request receipt to fulfillment, auditability, and error handling.
A data subject request (DSR) arrives via a form, API, email, or portal. The system must capture the request, validate it, and store it in a structured format. The request includes a *request type* (access, rectification, erasure, portability, restriction, objection), *data subject identifier* (email, ID, or subject reference), and *additional context*.
The core data model is a data_subject_request table in a relational database:
CREATE TABLE data_subject_request (
id UUID PRIMARY KEY,
request_type TEXT NOT NULL CHECK (request_type IN (
'access', 'rectification', 'erasure', 'portability', 'restriction', 'objection'
)),
subject_identifier TEXT NOT NULL,
subject_reference TEXT,
requested_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
metadata JSONB,
status TEXT NOT NULL DEFAULT 'received' CHECK (status IN (
'received', 'processing', 'completed', 'failed', 'cancelled'
)),
error TEXT,
processed_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ
);
Requests are ingested via a REST endpoint:
POST /api/v1/data-subject-requests
Content-Type: application/json
{
"request_type": "access",
"subject_identifier": "[email protected]",
"subject_reference": "usr-7a3d1f",
"metadata": {
"reason": "User wants to export all data for a job application",
"priority": "high"
}
}
Response:
{
"request_id": "b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f",
"status": "received",
"received_at": "2024-05-15T14:32:01.234Z",
"links": {
"self": "/api/v1/data-subject-requests/b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f",
"status": "/api/v1/data-subject-requests/b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f/status"
}
}
The request is queued for processing using a message broker (e.g., Apache Kafka, RabbitMQ, or AWS SQS). Each message includes the request_id and subject_identifier, and triggers a worker service.
Processing begins when a worker pulls a request from the queue. The worker checks the data_subject_request table and transitions the status to processing.
For access requests, the worker queries all data sources—user profiles, transactions, logs, files—and compiles a response. Each source may have a different schema and interface.
To retrieve user profile data:
curl -H "Authorization: Bearer ${API_TOKEN}" \
"https://api.example.com/v1/users/[email protected]?include=preferences,settings,activity"
Response:
{
"user_id": "usr-7a3d1f",
"email": "[email protected]",
"name": "Jane Doe",
"preferences": {
"theme": "dark",
"notifications": true
},
"settings": {
"timezone": "Europe/Berlin",
"language": "en"
},
"activity": [
{ "action": "login", "timestamp": "2024-04-20T08:15:00Z" },
{ "action": "updated_profile", "timestamp": "2024-05-05T12:03:00Z" }
]
}
For erasure (deletion), the worker marks data as deleted in all systems. This includes:
deleted_at in the users tableorders, comments, filesDELETE requests to external APIscurl -X DELETE \
-H "Authorization: Bearer ${API_TOKEN}" \
"https://api.example.com/v1/orders?user_id=usr-7a3d1f"
For portability, the worker generates a ZIP archive containing all data in a standardized format (e.g., JSON, CSV, XML), with a manifest file.
mkdir -p /tmp/portability/[email protected]
cp -r /data/user/7a3d1f/* /tmp/portability/[email protected]/
cat > /tmp/portability/[email protected]/manifest.json << 'EOF'
{
"subject": "[email protected]",
"request_id": "b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f",
"created_at": "2024-05-15T14:32:01.234Z",
"data": [
{ "type": "user_profile", "path": "user_profile.json" },
{ "type": "orders", "path": "orders.json" },
{ "type": "activity_logs", "path": "activity_logs.csv" }
]
}
EOF
cd /tmp/portability && zip -r [email protected] .
The final ZIP is stored in a cloud object store (e.g., AWS S3, Google Cloud Storage) and a URL is recorded in the request.
Once processing completes, the worker updates the data_subject_request record:
UPDATE data_subject_request
SET
status = 'completed',
processed_at = NOW(),
completed_at = NOW(),
metadata = jsonb_set(metadata, '{results}', '{
"files": [
{
"name": "user_profile.json",
"size": 2048,
"url": "https://storage.example.com/exports/[email protected]"
}
],
"total_records": 147,
"sources": [
"user_service",
"order_service",
"activity_logs"
]
}'::jsonb)
WHERE id = 'b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f';
The system sends a notification to the user via email, API, or in-app message. The notification includes:
Example email body:
Subject: Your GDPR Data Subject Request Has Been Processed
Hi Jane,
Your request to access your personal data has been completed.
Details:
- Request type: Access
- Request ID: b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f
- Processed on: 2024-05-15 at 14:35:12 UTC
Your data is available in a downloadable archive. It includes:
- Your profile and settings
- All your orders
- Activity logs
Download your data: https://example.com/export/b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f
You can also view the full response and audit trail at:
https://example.com/requests/b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f
This response complies with GDPR Article 15 (Right of Access).
— The Compliance Team
The system also emits an event to a central audit stream:
{
"event_type": "dsr_fulfilled",
"request_id": "b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f",
"subject_identifier": "[email protected]",
"request_type": "access",
"duration_ms": 2437,
"output_files": [
"user_profile.json",
"orders.json",
"activity_logs.csv"
],
"data_sources": [
"user_service",
"order_service",
"activity_logs"
],
"timestamp": "2024-05-15T14:35:12.000Z"
}
Failure is not a deviation—it is a core part of the process. When a request fails, the system logs the error in the data_subject_request table and queues a retry.
Common failure modes:
Example error record:
UPDATE data_subject_request
SET
status = 'failed',
error = 'Failed to generate portability ZIP: error: failed to write /tmp/portability/[email protected]: No space left on device',
processed_at = NOW(),
completed_at = NOW()
WHERE id = 'b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f';
The worker runs a retry loop:
#!/bin/bash
while true; do
# Poll for unprocessed requests
psql -c "SELECT id, subject_identifier FROM data_subject_request WHERE status = 'received' LIMIT 10" | while read id subject; do
# Mark as processing
psql -c "UPDATE data_subject_request SET status = 'processing' WHERE id = '$id'"
# Run request processor
if ! ./process-dsr.sh "$id" "$subject"; then
# Log error
psql -c "UPDATE data_subject_request SET status = 'failed', error = $1 WHERE id = '$id'"
else
# Mark as completed
psql -c "UPDATE data_subject_request SET status = 'completed', processed_at = NOW() WHERE id = '$id'"
fi
done
sleep 5
done
Retries are exponential backoff: after first failure, wait 10 seconds, then 30, then 60, then 120. After five attempts, the request is escalated to a human reviewer.
For rectification, the user submits corrections (e.g., change of address, update of email). The system validates the input and applies it across all relevant systems.
Input example:
{
"request_type": "rectification",
"subject_identifier": "[email protected]",
"metadata": {
"updates": {
"email": "[email protected]",
"address": {
"street": "123 Main St",
"city": "San Francisco",
"postal_code": "94105",
"country": "US"
}
}
}
}
The system updates the user in all systems:
curl -X PUT \
-H "Authorization: Bearer ${API_TOKEN}" \
-d '{"email": "[email protected]", "address": {"city": "San Francisco"}}' \
"https://api.example.com/v1/users/[email protected]"
For restriction, the system applies a restricted flag to the user and blocks certain actions (e.g., no new orders until restriction is lifted). The restriction is stored in the users table:
ALTER TABLE users ADD COLUMN restriction_reason TEXT;
ALTER TABLE users ADD COLUMN restriction_expires_at TIMESTAMPTZ;
The system checks for active restrictions before executing key operations. For example, when creating an order:
SELECT 1 FROM users u
WHERE u.email = '[email protected]'
AND u.restriction_expires_at > NOW();
If the user is restricted, the operation fails with:
HTTP/1.1 403 Forbidden
Content-Type: application/json
{
"error": "Data subject restriction active",
"details": "User cannot place orders until 2025-01-15",
"restriction_expires_at": "2025-01-15T00:00:00Z",
"request_id": "b9d4f8e2-3b1a-4c5d-8e7f-1a2b3c4d5e6f"
}
Every data subject request is tracked across systems. The audit trail includes:
The data_subject_request table serves as the central hub. Each metadata field is used to store additional context, such as:
request_id for tracking across systemsdata_sources for reportingresults for validation and compliance checkstimeline for performance analysisA compliance dashboard can query this table to show:
user_id. The system must map identifiers via a user_identifiers table.The system must validate all DSRs before marking them as completed. Validation includes:
Validation is automated via a validate-dsr service that runs after each request completion.
Engineering GDPR data subject rights means building a system that receives, processes, validates, and reports on user requests with precision and traceability. The core is a well-defined, transactional data_subject_request table, supported by event-driven processing, error handling, and auditability. The system must handle all six rights—access, rectification, erasure, portability, restriction, objection—with consistent interfaces, clear error reporting, and reliable data delivery.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy