How the GDPR Storage Erasure Endpoint Handles Data Deletion in Open SEO

The GDPR storage erasure endpoint at /api/internal/gdpr-erasure/storage authenticates requests via HMAC signature verification, validates payloads against a Zod schema, then permanently deletes user data from database tables and cloud storage before returning a success confirmation.

The GDPR storage erasure endpoint is a private internal API in the every-app/open-seo repository designed for compliance with data deletion requirements. Unlike public-facing endpoints, this handler requires cryptographic authentication and performs irreversible data removal across multiple storage layers. This article examines the complete implementation based on the source code in the main branch.

Authentication and Request Verification

The endpoint implements a two-layer security model using timestamped HMAC signatures. Every request must prove it originated from an authorized internal service.

Header Extraction and Signature Validation

When a POST request arrives at /api/internal/gdpr-erasure/storage, the handler at src/server/gdpr/storage-erasure.ts immediately extracts two required headers:

  • x-gdpr-timestamp — prevents replay attacks by enforcing request freshness
  • x-gdpr-signature — proves the payload was signed by a trusted party

The server invokes signGdprErasureRequest from src/shared/gdpr-erasure.ts to recompute the expected signature from the payload, timestamp, and a configured secret key. If the supplied signature fails to match, the endpoint returns 401 Unauthorized without processing further.

// From src/shared/gdpr-erasure.ts
export async function signGdprErasureRequest(
  payload: GdprStorageErasurePayload,
  timestamp: string
): Promise<string> {
  const message = `${timestamp}.${JSON.stringify(payload)}`;
  return createHmac('sha256', process.env.GDPR_SECRET_KEY)
    .update(message)
    .digest('hex');
}

Payload Schema Validation

Before any deletion occurs, the request body undergoes strict validation against gdprStorageErasurePayloadSchema defined in src/shared/gdpr-erasure.ts. This Zod schema ensures:

  • userId is present and formatted correctly
  • startedByUserId identifies the authorized initiator
  • All required metadata fields are provided

Validation failures immediately trigger a 400 Bad Request response, preventing malformed or incomplete erasure attempts.

// Schema definition location: src/shared/gdpr-erasure.ts
export const gdprStorageErasurePayloadSchema = z.object({
  userId: z.string().uuid(),
  startedByUserId: z.string().uuid(),
  reason: z.enum(['user_request', 'account_deletion', 'legal_requirement']),
  requestedAt: z.string().datetime(),
});

Multi-Layer Data Deletion Process

Once authenticated and validated, the handler executes a cascading deletion across all data tiers. The implementation in src/server/gdpr/storage-erasure.ts orchestrates this through storage-layer functions.

Database Row Removal

The erasure first targets relational data:

  • users table — primary user record
  • projects — all SEO projects owned by the user
  • keywords — tracked keyword entries
  • serp_snapshots — historical search result data

These deletions use hard DELETE operations rather than soft flags, ensuring true data elimination per GDPR Article 17 requirements.

Cloud Storage Cleanup

Beyond the database, the handler removes:

  • Uploaded files and blobs from configured cloud storage (D1 or Postgres-backed object stores)
  • Any derivative exports or report archives linked to the user ID

Cache Invalidation

The implementation optionally clears cached data indexed by user ID, preventing stale data from persisting in Redis or edge caches.

Error Handling and Observability

The endpoint wraps all deletion operations in comprehensive error handling. Any exception during the process:

  1. Is logged via console.error with full stack traces
  2. Is reported through captureServerError with the tag source: "gdpr_storage_erasure"
  3. Returns a generic 500 Internal Server Error to callers (avoiding information disclosure)

This observability ensures audit trails for compliance reviews while protecting internal implementation details.

Response Format

Successful completion returns a minimal confirmation:

{
  "ok": true
}

The 200 OK response indicates all storage tiers have been processed and the user's data is permanently erased.

Route Registration and Integration

The endpoint is wired into the main application router in src/server.ts:

import { handleGdprStorageErasure } from "@/server/gdpr/storage-erasure";
import { GDPR_STORAGE_ERASURE_PATH } from "@/shared/gdpr-erasure";

router.post(GDPR_STORAGE_ERASURE_PATH, handleGdprStorageErasure);

This centralized registration pattern keeps routing logic explicit and enables straightforward middleware application.

Calling the Endpoint Programmatically

From Internal Services (TypeScript)

import { signGdprErasureRequest } from "./src/shared/gdpr-erasure";

const payload = {
  userId: "12345",
  startedByUserId: "gdpr-deleted-user",
  reason: "user_request",
  requestedAt: new Date().toISOString(),
};

const timestamp = Date.now().toString();
const signature = await signGdprErasureRequest(payload, timestamp);

const response = await fetch(
  "https://your-domain.com/api/internal/gdpr-erasure/storage",
  {
    method: "POST",
    headers: {
      "content-type": "application/json",
      "x-gdpr-timestamp": timestamp,
      "x-gdpr-signature": signature,
    },
    body: JSON.stringify(payload),
  }
);

const result = await response.json();
console.log("Erasure confirmed:", result.ok);

Via CLI Utility

For operational use, the repository includes a dedicated script at scripts/erase-user-data.ts:

pnpm gdpr:erase-user --email user@example.com

This CLI builds the signed payload automatically using environment-configured credentials and handles the complete request lifecycle with formatted output.

Key Implementation Files

File Purpose
src/shared/gdpr-erasure.ts Zod schema definitions and HMAC signing utilities
src/server/gdpr/storage-erasure.ts HTTP handler implementing verification and deletion logic
src/server.ts Route registration with the main application router
scripts/erase-user-data.ts CLI tool for operational erasure requests
runbooks/gdpr-erasure.md Operational documentation for manual procedures and audit requirements

Summary

  • Authentication: HMAC signature verification via signGdprErasureRequest in src/shared/gdpr-erasure.ts prevents unauthorized erasure attempts
  • Validation: Zod schema enforcement rejects malformed payloads before any data modification
  • Deletion scope: Cascading removal across database tables, cloud storage, and caches ensures complete data elimination
  • Observability: Structured error reporting with captureServerError maintains compliance audit trails
  • Integration: Route registration in src/server.ts provides clean architectural separation

Frequently Asked Questions

What happens if the signature verification fails?

The endpoint returns 401 Unauthorized immediately without processing the request body. This prevents timing attacks and ensures only properly signed internal requests can trigger data deletion.

Can this endpoint be called from external systems?

No. The endpoint is designed exclusively for internal platform workflows. The HMAC signing requires access to GDPR_SECRET_KEY, which is not exposed to external callers. External GDPR requests must flow through the platform's public-facing request intake system.

What database tables are affected by the erasure?

The handler removes data from the users table and all related entities including projects, keywords, and serp_snapshots. The exact table list is defined in the storage-layer functions called from src/server/gdpr/storage-erasure.ts.

How can operators verify an erasure completed successfully?

Operators can check server logs for the source: "gdpr_storage_erasure" tag in error tracking systems. Successful requests return {"ok": true}. For audit purposes, the runbooks/gdpr-erasure.md document outlines expected verification steps and post-erasure confirmation procedures.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →