Consent Widget
The DPDPA Shield consent widget is the user-facing component of your compliance setup. It presents consent notices, records decisions, and provides ongoing preference management - all in compliance with DPDPA 2023 Section 5 and 6.
Widget states
The widget has four visual states. Understanding each helps you configure and test your integration.
Compact Banner
First visit
Shown on first visit or when no consent is stored. Displays purpose summary with Accept All, Reject All, and Manage Preferences buttons.
Manage Preferences
Full preference panel
Full purpose-by-purpose preference panel. Each consent-based purpose has an individual toggle. Non-consent purposes (Contract / Legal Obligation) are shown as non-toggleable.
Rejection Confirmation
2.5 second auto-dismiss
Shown for 2.5 seconds after Reject All or saving all-off preferences. Confirms that non-essential processing has stopped before the widget closes.
Persistent Privacy Button
Always visible after consent
Small 36px floating shield button that stays visible after the widget is dismissed. Allows users to manage or withdraw consent at any time. Tooltip: Manage or withdraw your consent.
How consent is recorded
The widget follows a two-phase recording pattern - optimistic local update then server confirmation.
First visit
User visits site for the first time
No localStorage consent record found
Compact banner appears
Fetches active notice via GET /public-notice
User makes a choice
Accept All / Reject All / Save Preferences
onConsent fires (optimistic)
Immediately updates localStorage - consentRecordId is undefined
POST /consent/sdk-record called
Server creates immutable ConsentRecord + R2 proof
onConsent fires again (confirmed)
consentRecordId is now the confirmed UUID
Widget dismisses, persistent button shown
Rejection shows 2.5s confirmation first
Return visit
User returns to site
localStorage has a consent record
Widget NOT shown
Consent is remembered - no banner interruption
Persistent button shown immediately
User can click to open Manage Preferences at any time
Widget positioning
Set position in DPDPAShield.init(). The widget and its persistent button both anchor to the same corner.
Click a position above to see its code snippet.
DPDPAShield.init({
apiKey: 'dpdpa_live_xxx',
position: 'bottom-right' // default
})Consent record status values
Consent records are immutable. Each new user action creates a new ConsentRecord. The most recent record is the current state.
| Status | Meaning |
|---|---|
ACCEPTED | All non-required purposes accepted. |
REJECTED | All non-required purposes rejected. |
PARTIAL | Mixed - some accepted, some rejected. |
Note: WITHDRAWN is a database-level status set when a user withdraws via the Rights Portal. It is not returned in the onConsent callback. To react to withdrawals in your backend, subscribe to the consent.withdrawn webhook event.
A record can also be partially withdrawn
When a Data Principal withdraws only some purposes, withdrawnAt stays null - the record is not fully withdrawn - while withdrawnPurposes accumulates the revoked purpose IDs. Checking only isWithdrawn will show false and incorrectly imply full consent is still active - always check isPartiallyWithdrawn too. See Analytics API below for the full response shape.
Checking consent in your application
Client-side (JavaScript)
After the SDK fires onConsent, read consent state directly from localStorage to gate analytics, tracking, or any third-party feature.
const API_KEY = process.env.NEXT_PUBLIC_DPDPA_API_KEY;
function getConsentStatus() {
const raw = localStorage.getItem('dpdpa_consent_' + API_KEY);
return raw ? JSON.parse(raw) : null;
// Returns: { id, status, ts, lang } or null
}
// The SDK writes dpdpa_purposes_{apiKey} to localStorage automatically
// after the API confirms the consent record.
// Shape: { [purposeId]: boolean }
function isPurposeConsented(purposeId) {
try {
const purposes = JSON.parse(
localStorage.getItem('dpdpa_purposes_' + API_KEY) || '{}'
);
return purposes[purposeId] === true;
} catch {
return false;
}
}
// Gate a feature behind consent
if (isPurposeConsented('YOUR_PURPOSE_ID')) {
// load analytics, enable tracking, etc.
}Server-side verification
Hash the user's email with SHA-256 and call the GET /consent/status/:hash endpoint to verify consent on your backend.
const crypto = require('crypto');
// Hash the user's email (same algorithm as SDK)
const hash = crypto
.createHash('sha256')
.update(email.toLowerCase().trim())
.digest('hex');
const response = await fetch(
`https://api.dpdpashield.in/api/v1/consent/status/${hash}`,
{
headers: {
'Authorization': `Bearer ${DPDPA_API_KEY}`,
'Content-Type': 'application/json'
}
}
);
const { data } = await response.json();
// data is an array of consent records, most recent first.
// Use data[0] for the current state; empty array = no consent on record.
const latest = data[0] ?? null;
// latest.consentGiven: Record<purposeId, boolean> | null
// latest.isWithdrawn: boolean - true only on FULL withdrawal
// latest.withdrawnAt: string | null
// latest.withdrawnPurposes: string[] - non-empty on a PARTIAL withdrawal
// latest.isPartiallyWithdrawn: boolean - check this too, not just isWithdrawnConsent withdrawal
DPDPA Section 6(4) requires withdrawal to be as easy as giving consent. DPDPA Shield provides three withdrawal mechanisms:
Persistent privacy button β Manage Preferences β red "β Withdraw all consent" link
For: All users on your website
dpdpashield.in/rights/[slug] β Withdraw Consent tab β Enter email β Confirm
For: Users who cannot find the widget
Set withdrawalUrl in your consent notice. The SDK renders a "Withdraw consent" link in the widget footer automatically.
For: Rule 3(c) compliance
Toggling preferences off is not the same as withdrawing
Turning off purpose toggles in Manage Preferences and clicking Save writes a new consent record with a REJECTED or PARTIAL outcome through the normal consent-recording path - it does not create a WithdrawalRecord and does not trigger the DPO email or ComplianceTask below. Only the explicit "Withdraw all consent" link and the Rights Portal / withdrawal-URL flows go through full withdrawal.
What happens when withdrawal is recorded
DPDPA Rule 3 disclosures in the widget
Rule 3 requires the consent notice to include contact details and a complaint mechanism. Configure these in your published notice - the widget renders them automatically:
| Field | What it does |
|---|---|
contactEmail | DPO email address shown in the widget footer below action buttons. |
complaintText | Grievance mechanism text shown in the compact and preferences views. |
withdrawalUrl | Link rendered as 'Withdraw consent' in the widget footer. Points users to the Rights Portal withdrawal tab. |
// Set these fields in the Notice Builder:
// Contact Email: privacy@yourdomain.com
// Complaint Text: "To raise a grievance, contact our DPO at privacy@yourdomain.com
// or write to: [postal address]. We will respond within 7 days."
// Withdrawal URL: https://dpdpashield.in/rights/your-slug?apiKey=dpdpa_live_xxx
// The SDK reads these from the /public-notice endpoint and renders them
// automatically - no code changes needed on your website.Consent Analytics
Query consent metrics and records via API. All endpoints require Authorization: Bearer {token} from your dashboard session or a server API key.
/api/v1/consent/analyticsReturns consent metrics for the tenant: counts by status, purpose breakdown, language distribution, and withdrawal rate.
Bearer token/api/v1/consent/recordsPaginated list of all ConsentRecords for the tenant, including the notice version at the time of consent and the current notice version. Use the version comparison to identify stale consent records.
pagePage number (default: 1)limitResults per page (default: 20)statusFilter: active (withdrawnAt is null and not partially withdrawn) | partial (withdrawnPurposes set, withdrawnAt still null) | withdrawn (withdrawnAt is set){
items: ConsentRecord[],
total: number,
page: number,
limit: number
}/api/v1/consent/status/:hashCheck consent status for a specific Data Principal. Hash the user's email with SHA-256 (lowercase + trimmed) before calling. Returns an array - one entry per notice the principal has a consent record for, most recent first.
hashSHA-256 hex of lowercased + trimmed email{
data: [{
consentRecordId: string,
noticeId: string,
consentGiven: Record<string, boolean>,
isWithdrawn: boolean,
withdrawnAt: string | null,
withdrawnPurposes: string[],
isPartiallyWithdrawn: boolean,
createdAt: string,
proofHash: string
}]
}
// empty array = no consent record for this principalOn this page