| name | consent-receipt-spec |
| description | Implement the Kantara Initiative consent receipt specification including machine-readable receipt structure, JWT-based verification mechanisms, receipt lifecycle management, and integration patterns for consent management platforms. Supports ISO/IEC 27560 consent record information structure. |
| license | Apache-2.0 |
| metadata | {"author":"mukul975","version":"1.0","domain":"privacy","subdomain":"privacy-engineering","tags":"consent-receipt, kantara-initiative, consent-management, jwt-verification, iso-27560"} |
Kantara Initiative Consent Receipt Specification
Overview
A consent receipt is a record of a consent transaction provided to the individual (data subject) as evidence that consent was given. The Kantara Initiative Consent Receipt Specification v1.1 defines a standard, machine-readable format that enables individuals to track and manage their consent across multiple organizations. This skill covers the implementation of consent receipts aligned with the Kantara specification and ISO/IEC 27560:2023.
Consent Receipt Structure
Core Fields (Kantara v1.1)
| Field | Type | Required | Description |
|---|
| version | String | Yes | Specification version (e.g., "KI-CR-v1.1.0") |
| jurisdiction | String | Yes | Legal jurisdiction (ISO 3166-1 alpha-2) |
| consentTimestamp | DateTime | Yes | UTC timestamp of consent grant |
| collectionMethod | String | Yes | How consent was collected (web form, verbal, paper) |
| consentReceiptID | UUID | Yes | Unique identifier for this receipt |
| publicKey | String | No | Public key for receipt verification |
| language | String | Yes | Language of the consent interaction (BCP 47) |
| piiPrincipalId | String | Yes | Identifier for the data subject |
| piiControllers | Array | Yes | List of data controllers |
| policyUrl | URL | Yes | Link to the privacy policy |
| services | Array | Yes | Services for which consent is given |
| sensitive | Boolean | Yes | Whether special category data is involved |
| spiCat | Array | No | Special categories of data processed |
PII Controller Object
| Field | Type | Required | Description |
|---|
| piiController | String | Yes | Name of the controller organization |
| onBehalf | Boolean | No | Whether acting on behalf of another controller |
| contact | String | Yes | Contact information for the controller |
| address | Object | Yes | Physical address of the controller |
| email | String | Yes | Contact email address |
| phone | String | No | Contact phone number |
| piiControllerUrl | URL | No | URL of the controller's website |
Service Object
| Field | Type | Required | Description |
|---|
| service | String | Yes | Name of the service |
| purposes | Array | Yes | List of processing purposes |
Purpose Object
| Field | Type | Required | Description |
|---|
| purpose | String | Yes | Description of the processing purpose |
| purposeCategory | Array | Yes | Category codes for the purpose |
| consentType | String | Yes | "explicit" or "implicit" |
| piiCategory | Array | Yes | Categories of PII processed |
| primaryPurpose | Boolean | Yes | Whether this is the primary purpose |
| termination | String | Yes | How consent can be withdrawn |
| thirdPartyDisclosure | Boolean | Yes | Whether data is shared with third parties |
| thirdPartyName | String | No | Name of third party (if applicable) |
Machine-Readable Receipt Format
JSON Consent Receipt
{
"version": "KI-CR-v1.1.0",
"jurisdiction": "EU",
"consentTimestamp": "2026-03-14T10:30:00.000Z",
"collectionMethod": "web_form",
"consentReceiptID": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"language": "en",
"piiPrincipalId": "user-98765",
"piiControllers": [
{
"piiController": "Cipher Engineering Labs",
"onBehalf": false,
"contact": "Data Protection Officer",
"address": {
"streetAddress": "100 Technology Drive",
"locality": "London"
JWT-Based Verification Mechanism
Signed Consent Receipt Implementation
"""
Consent receipt generation and verification using JWT (JSON Web Tokens).
Implements Kantara Initiative Consent Receipt Specification v1.1
with cryptographic verification.
"""
import json
import uuid
from datetime import datetime, timezone
from typing import Optional
import jwt
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.backends import default_backend
class ConsentReceiptIssuer:
"""
Issue and sign consent receipts as JWTs.
Uses RS256 (RSA with SHA-256) for signing.
"""
def __init__(self, private_key_pem: bytes, issuer_name: str):
"""
Args:
private_key_pem: PEM-encoded RSA private key
issuer_name: Name of the issuing organization
"""
self.private_key = serialization.load_pem_private_key(
private_key_pem, password=None, backend=default_backend()
)
self.public_key = self.private_key.public_key()
self.issuer_name = issuer_name
def issue_receipt(
self,
principal_id: str,
services: list[dict],
jurisdiction: str,
collection_method: str,
policy_url: str,
sensitive: = ,
language: = ,
controller_info: =
) -> [, ]:
receipt_id = (uuid.uuid4())
controller_info :
controller_info = {
: .issuer_name,
: ,
: ,
:
}
receipt_payload = {
: ,
: jurisdiction,
: datetime.now(timezone.utc).isoformat(),
: collection_method,
: receipt_id,
: language,
: principal_id,
: [controller_info],
: policy_url,
: services,
: sensitive,
: [],
: .issuer_name,
: principal_id,
: (datetime.now(timezone.utc).timestamp()),
: receipt_id,
}
signed_token = jwt.encode(
receipt_payload,
.private_key,
algorithm=,
headers={: }
)
receipt_id, signed_token
() -> :
.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
=serialization.PublicFormat.SubjectPublicKeyInfo
).decode()
:
():
.trusted_keys: [, ] = {}
():
.trusted_keys[issuer_name] = public_key_pem.encode()
() -> [, [], []]:
:
unverified = jwt.decode(token, options={: })
issuer = unverified.get()
issuer .trusted_keys:
(, , )
public_key = serialization.load_pem_public_key(
.trusted_keys[issuer],
backend=default_backend()
)
decoded = jwt.decode(
token,
public_key,
algorithms=[],
issuer=issuer
)
required_fields = [
, , ,
, , ,
, , ,
]
missing = [f f required_fields f decoded]
missing:
(, decoded, )
(, decoded, )
jwt.ExpiredSignatureError:
(, , )
jwt.InvalidSignatureError:
(, , )
jwt.DecodeError e:
(, , )
Receipt Lifecycle Management
Lifecycle States
ISSUED --> ACTIVE --> WITHDRAWN
| | |
| v v
| UPDATED ARCHIVED
| |
v v
EXPIRED ACTIVE (new version)
Lifecycle Manager
"""
Manage the lifecycle of consent receipts including
updates, withdrawals, and expiration tracking.
"""
from dataclasses import dataclass
from datetime import datetime, timezone
from enum import Enum
class ReceiptStatus(Enum):
ISSUED = "issued"
ACTIVE = "active"
UPDATED = "updated"
WITHDRAWN = "withdrawn"
EXPIRED = "expired"
ARCHIVED = "archived"
@dataclass
class ReceiptRecord:
receipt_id: str
principal_id: str
status: ReceiptStatus
issued_at: datetime
last_updated: datetime
withdrawn_at: datetime | None
jwt_token: str
version: int
superseded_by: str | None
purposes: list[str]
class ConsentReceiptLifecycle:
"""
Manage consent receipt state transitions and history.
"""
def __init__(self, receipt_store, issuer: ConsentReceiptIssuer):
self.store = receipt_store
self.issuer = issuer
def activate_receipt(self, receipt_id: str) -> bool:
"""Transition a receipt from ISSUED to ACTIVE."""
record = self.store.get(receipt_id)
record record.status == ReceiptStatus.ISSUED:
record.status = ReceiptStatus.ACTIVE
record.last_updated = datetime.now(timezone.utc)
.store.update(record)
() -> | :
old_record = .store.get(receipt_id)
old_record old_record.status [
ReceiptStatus.ACTIVE, ReceiptStatus.ISSUED
]:
new_receipt_id, new_jwt = .issuer.issue_receipt(
principal_id=old_record.principal_id,
services=updated_services,
jurisdiction=jurisdiction,
collection_method=,
policy_url=policy_url
)
new_record = ReceiptRecord(
receipt_id=new_receipt_id,
principal_id=old_record.principal_id,
status=ReceiptStatus.ACTIVE,
issued_at=datetime.now(timezone.utc),
last_updated=datetime.now(timezone.utc),
withdrawn_at=,
jwt_token=new_jwt,
version=old_record.version + ,
superseded_by=,
purposes=[p[] s updated_services p s.get(, [])]
)
.store.save(new_record)
old_record.status = ReceiptStatus.UPDATED
old_record.superseded_by = new_receipt_id
old_record.last_updated = datetime.now(timezone.utc)
.store.update(old_record)
new_receipt_id
() -> :
record = .store.get(receipt_id)
record record.status [
ReceiptStatus.ACTIVE, ReceiptStatus.ISSUED
]:
record.status = ReceiptStatus.WITHDRAWN
record.withdrawn_at = datetime.now(timezone.utc)
record.last_updated = datetime.now(timezone.utc)
.store.update(record)
() -> [ReceiptRecord]:
.store.find_by_principal(
principal_id, status=ReceiptStatus.ACTIVE
)
() -> [ReceiptRecord]:
.store.find_by_principal(principal_id)
ISO/IEC 27560:2023 Alignment
| Kantara CR Field | ISO 27560 Equivalent | Notes |
|---|
| consentReceiptID | Consent Record Identifier | Unique identifier for the record |
| consentTimestamp | Date and time of consent | When consent was collected |
| piiPrincipalId | PII Principal Identifier | Data subject identifier |
| piiControllers | PII Controller | Organization processing data |
| services.purposes | Purpose(s) | Processing purposes |
| services.purposes.piiCategory | PII Categories | Types of personal data |
| sensitive | Sensitive PII indicator | Special category flag |
| jurisdiction | Jurisdiction | Applicable legal framework |
| collectionMethod | Mechanism of consent | How consent was obtained |
| policyUrl | Privacy notice reference | Link to privacy notice |
References
- Kantara Initiative Consent Receipt Specification v1.1.0 (2018)
- ISO/IEC 27560:2023 — Privacy Technologies — Consent Record Information Structure
- ISO/IEC 29184:2020 — Guidelines for Online Privacy Notices and Consent
- RFC 7519 — JSON Web Token (JWT)
- W3C Data Privacy Vocabularies and Controls Community Group
- IEEE P7012 — Standard for Machine Readable Personal Privacy Terms