Bring in consent given earlier
Record consent collected before you used the product, from your server, with its date, basis and notice, without replacing newer answers.
Send each earlier consent from your server with collectedAt, the time it was given. A write that would replace a newer answer, a withdrawal or an erasure is refused and changes nothing. For a file, the console's Import consent from a file does the same.
Send each row#
-
Use a secret key. Only a secret key may send
collectedAtand the fields below. -
Send the row with
collectedAtand an idempotency key taken from the source row.Node.jsJavaScript import { isSkippedImport } from '@piedap/consent/server'; try { await client.recordConsent({ formKey: 'kaveri-account-opening', subjectId: 'CIF00012345', subjectType: 'USER_ID', identifiers: [{ subjectType: 'PHONE', subjectId: '+919876543210' }], decisions: [ { purposeKey: 'offers-by-sms', granted: true }, { purposeKey: 'offers-by-email', granted: false }, ], countryCode: 'IN', collectedAt: '2019-03-14T04:30:00.000Z', basis: 'PRE_COMMENCEMENT', noticeReference: 'AOF-2019 rev 3', lastActivityAt: '2026-07-02T11:20:00.000Z', idempotencyKey: 'aof-2019-row-4812', }); } catch (error) { if (!isSkippedImport(error)) throw error; logSkip('row 4812', error.code); }Python from piedap_consent import ( ConsentError, Decision, Identifier, is_skipped_import, ) try: client.record_consent( form_key="kaveri-account-opening", subject_id="CIF00012345", subject_type="USER_ID", identifiers=[Identifier("PHONE", "+919876543210")], decisions=[ Decision("offers-by-sms", True), Decision("offers-by-email", False), ], country_code="IN", collected_at="2019-03-14T04:30:00Z", basis="PRE_COMMENCEMENT", notice_reference="AOF-2019 rev 3", last_activity_at="2026-07-02T11:20:00Z", idempotency_key="aof-2019-row-4812", ) except ConsentError as error: if not is_skipped_import(error): raise log_skip("row 4812", error.code)cURLShell curl -X POST https://console.dpconsent.com/consent/sdk/v1/consent \ -H "Authorization: Bearer $PIEDAP_SECRET_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: aof-2019-row-4812" \ -d '{ "formKey": "kaveri-account-opening", "subjectId": "CIF00012345", "subjectType": "USER_ID", "identifiers": [ { "subjectType": "PHONE", "subjectId": "+919876543210" } ], "decisions": [ { "purposeKey": "offers-by-sms", "granted": true }, { "purposeKey": "offers-by-email", "granted": false } ], "countryCode": "IN", "collectedAt": "2019-03-14T04:30:00.000Z", "basis": "PRE_COMMENCEMENT", "noticeReference": "AOF-2019 rev 3", "lastActivityAt": "2026-07-02T11:20:00.000Z" }' -
Log each skipped row with its code, and move on to the next row.
Stay under the key's budget: under ten writes a second for a test key. Use a key without consent:check. On a key that can check, each write sent with collectedAt also counts each identifier it names (subjectId and identifiers) against Checks per minute, and answers 429 CHECK_BUDGET_EXCEEDED over it.
A row that sends dataPrincipalType for a child or a person with a lawful guardian needs guardian, even when the record already holds the person.
Mark consent given before the Act#
| Field | What it holds |
|---|---|
basis |
PRE_COMMENCEMENT: your statement that the person consented before the DPDP Act commenced. |
noticeReference |
The name or number of the notice the person saw, up to 120 letters, digits, spaces and . _ : / # ( ) -. Never personal data. |
noticeSha256 |
The SHA-256 of that notice, 64 hex characters. |
All three are sealed into the record and shown on it, its receipt and every consent webhook. A later live answer or a change through PATCH clears them. A withdrawal or an erasure keeps them.
Your statement, for your counsel
The product records the basis you state and does not check it. The label and its use are for your counsel to review.
Handle rows that are skipped#
A skipped row answers 409, writes nothing and sends no webhook. None is worth retrying.
| Code | The records already hold |
|---|---|
CONSENT_WITHDRAWN_ON_RECORD |
A withdrawal on this form, whatever its date |
CONSENT_ALREADY_ON_RECORD |
An answer with this exact collectedAt, so the row was imported before |
CONSENT_NEWER_ON_RECORD |
A newer answer on this form |
CONSENT_ERASED_ON_RECORD |
An erasure of the person at or after collectedAt |
CONSENT_HELD_UNDER_OTHER_IDENTIFIER |
A withdrawal, the same answer or a newer one under another identifier of the person |
The last code looks at every identifier the row names in identifiers, and every identifier linked to one of them. Nothing else: a person known only under an identifier the row neither names nor links is out of reach. errorData names the record and its current state. The Node.js helper isSkippedImport and the Python is_skipped_import are true for these five codes only.
A paper or branch consent given again after a withdrawal is a new answer, not an earlier one. Send it without collectedAt. Keep the date on the paper in capture.methodReference or metadata. See Record consent taken in a branch, by phone or on paper.
Dates and phone numbers#
- Each decision's date is
collectedAt. The record expires after the form's term, counted from the day of the call, unless you sendexpiresAt. expiresAtmust be after the call and within 3,650 days of it.lastActivityAtis when the person last used your service, for inactivity erasure. It must fall betweencollectedAtand now.collectedAtmust not be in the future or before 1990.- Phone numbers must be in E.164, such as
+919876543210, or the row answers400 PHONE_NOT_E164.
The console import sends no webhook for each record, only one consent.imported. The file's SHA-256 on an import run is what the browser reported. It does not prove which rows were written.
Related#
- Consent, every field and error
- Consent import file
- Identifiers