How to Build a DPDP-Compliant Consent Manager: System Design & Database Schema
Section 6 of the Digital Personal Data Protection Act, 2023 (DPDP Act) mandates that consent must be free, specific, informed, unconditional, and unambiguous with an affirmative action. Furthermore, under Section 6(7), the Act establishes the statutory framework for Consent Managers — entities registered with the Data Protection Board of India (DPBI) to manage, review, and withdraw consent interoperably.
Whether you are preparing to integrate with registered Consent Managers or building an internal, audit-proof consent engine for your platform, this guide details the system architecture, database schema, and event-driven pipelines required for compliance.
1. Architectural Principles of DPDP Consent
A compliant consent system must satisfy four engineering guarantees:
- Granularity (Unbundled Consent): Each processing purpose (identity verification, order fulfillment, promotional newsletters, behavioral telemetry) must be captured as a distinct, independent permission.
- Immutability (Tamper-Proof Audit Trail): Every consent grant, denial, or withdrawal must be recorded as an append-only event with cryptographic hashing.
- Reversibility (Zero Friction Withdrawal): Withdrawing consent must be as simple as granting it (e.g. an in-app toggle), and must propagate to downstream processing services in near real-time.
- Multilingual Notice Association: Every recorded consent record must link to the exact version and language of the notice presented to the user at that timestamp.
2. High-Level Architecture Diagram
[Client Application / Frontend]
│
│ POST /api/v1/consent/record
▼
[API Gateway / Auth]
│
▼
[Consent Orchestrator] ─── Verifies Notice Version & User State
│
├── Writes to PostgreSQL (Append-Only Event Ledger)
│
└── Publishes to Redis / BullMQ ("consent.event")
│
├── Workers Update Active Cache (Redis Key-Value)
├── Workers Trigger Downstream PII Deletion (if withdrawn)
└── Webhooks Dispatched to 3rd-Party Processors
3. Relational Database Schema (PostgreSQL)
To survive a regulatory inquiry from the Data Protection Board, you must prove who consented to what, when, under which notice version, and in which language.
-- 1. Master Table: Specific Business Purposes
CREATE TABLE processing_purposes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
code VARCHAR(64) UNIQUE NOT NULL, -- e.g. 'AUTH_CORE', 'MARKETING_SMS'
category VARCHAR(64) NOT NULL,
is_essential BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
-- 2. Notice Versions Table (Section 5 Compliance)
CREATE TABLE consent_notices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
version VARCHAR(32) NOT NULL,
language_code VARCHAR(8) NOT NULL DEFAULT 'en', -- e.g. 'en', 'hi', 'bn', 'ta'
title TEXT NOT NULL,
notice_body TEXT NOT NULL,
effective_from TIMESTAMPTZ NOT NULL,
effective_until TIMESTAMPTZ,
UNIQUE(version, language_code)
);
-- 3. Append-Only Consent Event Ledger
CREATE TABLE consent_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
data_principal_id UUID NOT NULL REFERENCES users(id),
purpose_id UUID NOT NULL REFERENCES processing_purposes(id),
notice_id UUID NOT NULL REFERENCES consent_notices(id),
status VARCHAR(16) NOT NULL CHECK (status IN ('GRANTED', 'WITHDRAWN', 'DENIED')),
ip_address INET NOT NULL,
user_agent TEXT NOT NULL,
state_hash VARCHAR(64) NOT NULL, -- SHA-256 of (user_id + purpose + status + timestamp)
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_consent_principal_purpose
ON consent_events(data_principal_id, purpose_id, created_at DESC);
4. Querying Current Effective Consent Fast
Because database read latency must remain sub-10ms for authenticated user flows, avoid executing a deep aggregate on consent_events for every incoming API request. Instead, cache the materialized state in Redis:
// Key format: consent:{dataPrincipalId}:{purposeCode}
// Value: 'GRANTED' | 'WITHDRAWN' | 'DENIED' with TTL
async function isConsentActive(principalId: string, purpose: string): Promise<boolean> {
const cacheKey = `consent:${principalId}:${purpose}`;
const cached = await redis.get(cacheKey);
if (cached !== null) {
return cached === 'GRANTED';
}
// Fallback to database if cache misses
const latestEvent = await db.consentEvents.findFirst({
where: { dataPrincipalId: principalId, purpose: { code: purpose } },
orderBy: { createdAt: 'desc' },
});
const isGranted = latestEvent?.status === 'GRANTED';
await redis.set(cacheKey, isGranted ? 'GRANTED' : 'WITHDRAWN', 'EX', 3600);
return isGranted;
}
5. Handling Consent Withdrawal Cascades
When a user toggles off consent for a marketing or analytics purpose, the system dispatches an asynchronous job:
// NestJS / BullMQ Consumer
@Processor('consent-events')
export class ConsentWithdrawalConsumer {
@Process('withdrawal')
async handleWithdrawal(job: Job<{ principalId: string; purposeCode: string }>) {
const { principalId, purposeCode } = job.data;
// 1. Invalidate Redis Cache
await this.redis.del(`consent:${principalId}:${purposeCode}`);
// 2. If purpose is analytics/tracking, purge behavioral telemetry
if (purposeCode === 'BEHAVIORAL_ANALYTICS') {
await this.telemetryService.purgeUserEvents(principalId);
}
// 3. Dispatch webhooks to third-party vendors (CRM, Email provider)
await this.webhookService.notifyVendorOfWithdrawal({
principalId,
purposeCode,
timestamp: new Date().toISOString(),
});
}
}
Summary
Building a DPDP-compliant Consent Manager is an architectural discipline that safeguards your company against ₹250 Crore penalties while building durable user trust.
For assistance in architecting or auditing your consent pipelines, contact Aman Kumar Singh or read our complete DPDP Act 2023 technical guide.
Key takeaways
- Consent must be modeled as an append-only event ledger with SHA-256 state hashes for legal non-repudiation.
- Cache active effective consent in Redis to achieve sub-10ms API authorization checks without querying historical event tables.
- Every consent record must associate with the exact notice version and language (English + 22 Scheduled languages) presented at the time.
- Consent withdrawal must be zero-friction and automatically trigger asynchronous BullMQ workers to purge downstream telemetry and notify third-party processors.
Frequently asked questions
What is a Consent Manager under Section 6(7)?
An interoperable entity registered with the Data Protection Board of India enabling individuals to give, manage, review, and withdraw consent across multiple Data Fiduciaries.
Why store consent as append-only events instead of a boolean column?
A boolean column (e.g. is_consented = true) does not capture historical changes, timestamps, notice versions, or IP addresses required during a regulatory inquiry.
How fast must consent withdrawal propagate?
The Act requires fiduciaries to cease processing within a reasonable time. Asynchronous queues should invalidate Redis caches and trigger downstream deletion within seconds.
Further reading
Related articles
Explore more on
Free tools for this topic

About the author
I'm Aman Kumar Singh, a software engineer in Noida, India building scalable full-stack products with React, Next.js, Node.js, NestJS, PostgreSQL, Redis, and AWS. I write about backend engineering, distributed systems, and system design.