Module · Consent

Consent that holds up when the Board asks for proof

Most consent implementations can tell you that someone agreed. Very few can tell you what they were shown, in which language, on which version of the notice, and what happened to their data when they changed their mind. That gap is the whole job, and it is what this module is built around.

Implements
DPDP §6, §6(4), §7
Record grain
One row per purpose decision
Languages
English + 18 machine-translated
In the product
Consent hub · six workspaces
Priya Gupta

What DPDP §6 and §7 actually require of a record

Section 6 makes consent free, specific, informed, unconditional and unambiguous, given by a clear affirmative action, and limited to the personal data necessary for the specified purpose. Section 7 sets out the legitimate uses that need no consent at all. The practical consequence is that a record has to answer two questions at once: what did this person agree to, and was that agreement attached to a purpose you were entitled to ask about.

So the storage grain here is one row per purpose decision, never one row per session. Each row pins the purpose and its version, the notice and its version, the language code the person actually read, the decision, the capture timestamp and the withdrawal timestamp if one exists. Alongside it the capture stores a SHA-256 hash of the exact notice text that was rendered, in that language — not a pointer to the current text, which will have changed by the time anyone asks.

Provenance sits on the same row: capture IP, user agent, a client-computed browser fingerprint, the first-party principal cookie, and a small evidence blob holding timezone, viewport, platform, browser language, colour scheme, do-not-track, the page URL and how long the person spent on the decision before acting. None of that proves intent on its own. Together it is the difference between a record and an assertion.

Per-purpose decisions, not one bundled tick

A purpose is a first-class object with its own code, description, retention period, data categories and lifecycle status, scoped to a business vertical. It is versioned: bumping a purpose creates version N+1 and marks the previous version superseded, so existing consents stay attached to the text they were given rather than silently inheriting new wording. A purpose that has carried consent activity cannot be deleted at all — it can only be retired.

Capture reflects that. The embedded widget submits granted and denied purpose codes as separate lists and writes a record for each. The hosted decision page will not accept a submission unless every purpose in the request has been decided, which is what stops a partially filled form from being read later as blanket agreement. The preference centre re-saves the whole set, so unticking something is a withdrawal rather than an omission — with the single exception of the essential session purpose, which is never withdrawable because the service cannot run without it.

  • Purposes carry their own retention period, which is what later drives the expiry clock.
  • Notices are composed from purposes, legitimate uses and an acknowledgement document, then published, retired or diffed against the previous version.
  • A notice can be imported from, or linked to, a published policy so the two do not drift apart.
  • Legitimate uses under §7 are defined in the same place, approved maker-checker, and shown on the notice as information rather than as something to tick.

Withdrawal must be as easy as consent — and it has to reach somewhere

Section 6(4) is short and unforgiving: withdrawing is to be as easy as giving. In practice that means more than one route. A signed revoke link goes out with the capture receipt; the rights portal accepts a withdrawal by organisation slug with an email and a one-time code, because there is no token to prove control; the widget and the preference centre both carry it; an operator can revoke on a person's behalf; and the integration API exposes it to systems that already know who the customer is. On the signed-link route a one-time code is demanded only when the original grant itself was code-verified, which keeps the effort symmetric instead of making withdrawal harder than consent was.

What happens next is the part that is usually missing. A withdrawal resolves the person's other records through email, phone, internal uid, fingerprint, cookie and client reference, flips them to withdrawn, and then creates one action per linked vendor according to the routing rules — delete, suppress, stop processing, or retain under a legal obligation. Each action carries its own default deadline, and actions still open past that deadline escalate by email to organisation administrators. A signed webhook fires on the same event so your own stack is not left polling.

  • Default action deadlines: delete 30 days, suppress 7, stop processing 7, retain under legal obligation 90.
  • Managed connectors update opt-out or communication-preference flags in Salesforce, HubSpot and Zoho CRM.
  • Webhook payloads are signed with an HMAC-SHA256 header over timestamp and body, and every delivery attempt is logged.
  • A background scan raises a single task for the DPO listing the data sources whose recorded PII types match the withdrawal. It flags; it never deletes anything by itself.

The consent artefact, and how far the proof actually goes

Evidence rows are written into a per-organisation SHA-256 chain. Each row hashes a canonical payload — event type, record, language, whether the notice audio was played, the stored notice text and audio keys, IP, user agent, timestamp and the previous row's hash — and takes its chain position under a row lock so two concurrent captures cannot claim the same slot. A verify call recomputes every hash in the organisation's chain and checks continuity.

On top of that sits the certificate: an identifier derived from the record and the latest payload hash, a signed URL, a public page a data principal can open, and a public verify endpoint that recomputes the hashes on demand. There is also a consent receipt built to ISO/IEC TS 27560 and the Kantara schema, carrying the masked principal, the controller, the purposes with their lawful basis and termination, the notice reference and version, the data life derived from the purpose's retention period, and the chain's latest hash and length. That receipt is issued as a signed JWS.

Two honest limits. The chain is tamper-evident, not tamper-proof: there is no external anchor and no keyed HMAC over the chain itself. And chain rows are written for the signed-link and QR capture flows, plus withdrawals and expiries — a consent captured purely through the widget or the hosted capture API has the full record and receipt but no chain row, so its certificate verify returns no evidence rather than a green tick. Treat the chain as the strongest artefact available on the flows that write it, not as a property of every capture.

Recognising a returning principal without re-prompting

Before rendering anything, the widget asks a state endpoint whether this browser already has an active decision, matching on a client-computed fingerprint and a 180-day first-party cookie scoped to the organisation. If it finds one, the banner stays down and the preference centre opens pre-ticked. An anonymous capture whose fingerprint or cookie later matches a record that carries an email or phone adopts that identity, which is how a decision made before sign-in survives sign-in.

For linking a person across systems the platform derives a non-reversible uid by hashing a tenant-chosen field with a per-tenant salt using scrypt; only a commitment to the salt is stored. The default match mode hashes in the browser so raw identifiers never reach the platform at all, and where a tenant nominates Aadhaar as the field it is hash-only and gated behind an explicit compliance acknowledgement.

Consent expires here, deliberately

Validity is the lesser of the purpose's retention period and one year, measured from capture. A sweep runs every six hours, flips lapsed records from active to expired, and writes an expiry event. Because the widget only recognises active records, an expired person sees the notice again rather than being counted as a standing yes — which is the behaviour you want and the one nobody builds.

A second, longer clock governs erasure rather than validity. When it fires the record is withdrawn with the retention policy named as the source, and it rides exactly the same vendor fan-out as a person-initiated withdrawal, so the downstream effect is identical. A freshness view scores the estate fresh, stale or overdue, and a re-consent campaign can be triggered when a notice or purpose changes: the email is drafted in English and Hindi, an administrator approves it, and it goes out as a signed link.

Where consent records break in real deployments

These are the failures that actually turn up, in roughly the order they turn up in.

  • A legacy signup form writes one boolean for five purposes. Every record it produced is unusable as evidence, and no amount of new tooling repairs it — those people have to be asked again.
  • Marketing installs a tag manager that fires analytics before the notice renders. The consent store is immaculate and the site is still processing without a basis; only a scan of the live page catches it.
  • Someone edits the notice text in place instead of publishing a new version. Every record captured before the edit now points at wording nobody was shown.
  • The banner is translated but the purposes are not, so a Tamil reader gets Tamil chrome around English purpose names. Coverage is per-purpose, and the translation coverage view exists precisely because this is easy to miss.
  • A withdrawal is honoured in the platform and nowhere else, because the vendor was never linked and so no action was created. The fan-out can only reach parties that are recorded as recipients.
  • Consent is captured under a purpose the business quietly repurposed six months later. The record is valid; the processing is not.

What the consent module does not do

It does not decide your lawful basis. Whether a purpose belongs under §6 consent or a §7 legitimate use is a legal judgement; the platform records the decision, versions it and shows you every purpose still marked unconfirmed, but it will not make the call.

It does not delete data out of your systems on its own. The withdrawal fan-out creates actions, dispatches to configured connectors, and raises a task naming the data sources that probably hold matching PII. Outbound dispatch to arbitrary systems is off unless an operator enables it per environment, and the discovery-side scan is detect-and-flag by design. Anything destructive stays behind a human.

It is not a certification, and it does not make an organisation compliant. It produces the records that let you demonstrate what you did. Our own security posture is SOC 2-aligned; that is a statement about us, not about your processing.

It does not translate for you in a legally binding sense. Machine translation covers eighteen of the 22 languages and manual edits survive re-runs, but a translated notice is still your notice and still needs a human who reads that language.

Consent management questions

Can a data principal withdraw consent for one purpose and keep the rest?

Yes. A withdrawal request can name specific records, and the preference centre re-saves the full set so unticking one purpose withdraws only that one. The only exception is the essential session purpose, which is not withdrawable because the service cannot operate without it. Each withdrawn purpose generates its own downstream vendor actions.

What happens to a consent record when we change the wording of a notice?

Notices and purposes are versioned rather than edited in place. Publishing a new version supersedes the old one and leaves existing records attached to the version they were captured against, and a diff shows what moved. If the change is material you can trigger a re-consent campaign against the affected population instead of assuming the old agreement carries over.

How long does a captured consent stay valid before it has to be refreshed?

Validity is the shorter of the purpose's own retention period and one year from capture. A sweep runs every six hours and moves lapsed records to expired. Because the banner only recognises active records, an expired person is shown the notice again rather than being treated as a continuing yes.

Does a withdrawal reach the vendors we shared the data with?

It creates one tracked action per linked vendor — delete, suppress, stop processing, or retain under a legal obligation — each with its own deadline, and escalates to administrators when an action runs late. Managed connectors can update opt-out flags in Salesforce, HubSpot and Zoho CRM. Reaching a vendor that was never recorded as a recipient is not something any system can do for you.

Can we capture consent from our own app without using the banner?

Yes. There is a hosted capture API with client credentials, an optional mandatory request signature and idempotency keys, plus signed links, QR posters and a preference centre. All of them write into the same record store, so the ledger, exports and the compliance pack cover every channel.

See your current consent posture

The website scanner grades the two consent domains — banner behaviour and consent mechanics — that together carry 30% of a site's DPDP score. It will tell you whether trackers fire before the notice renders, which is the single most common finding.

Scan your site's consent surface →

Priya Gupta — Priya writes on compliance operations at ProtectComply, including data principal rights, grievance handling and sector-specific programmes.