Production engineering guide for consent management platforms covering patterns, implementation strategies, and operational best practices.
Consent Management Platforms (CMPs) are systems that enable organisations to collect, manage, store, and enforce user consent across digital touchpoints, primarily in the context of data privacy regulations such as GDPR, CCPA, and LGPD. They matter when a product must track and act on user consent in real time—during sign-up, on web pages, in mobile apps, or across a user’s journey through a service. A CMP is not a configuration step in a data pipeline; it is a dynamic service that governs how and when data may be processed, shared, or retained based on user choices.
Consent begins when a user interacts with a consent prompt—typically a banner, modal, or embedded component—and continues through the collection, validation, storage, and enforcement of their choices. The core workflow involves three phases: consent capture, consent state tracking, and consent enforcement.
A consent capture event starts when a user interacts with a consent UI. The platform must record the chosen purposes, vendors, and legal bases, along with metadata such as timestamp, IP address, and user agent. The most common interface is an HTTP-based consent form served via a script tag or embedded iframe.
<script id="consent-manager" src="https://cmp.example.com/embed.js" async>
window.CMPConfig = {
version: "1.0",
locale: "en",
purposeLabels: {
1: "Essential",
2: "Analytics",
3: "Marketing",
4: "Personalization"
},
vendorLabels: {
101: "Google Analytics",
102: "Facebook Pixel",
103: "Hotjar"
},
defaultConsent: {
purposes: [1, 2],
vendors: [101, 102],
expiry: "180 days"
}
};
</script>
The script asynchronously loads a consent UI, populates it from CMPConfig, and sends a POST /api/consents request upon user action.
POST /api/consents HTTP/1.1
Host: cmp.example.com
Content-Type: application/json
Authorization: Bearer abc123
{
"userId": "u_456789",
"sessionId": "s_987654",
"consents": [
{
"purposeId": 1,
"consentGiven": true,
"timestamp": "2024-09-15T12:34:56Z",
"source": "modal",
"ipAddress": "192.168.1.10",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
},
{
"purposeId": 2,
"consentGiven": true,
"timestamp": "2024-09-15T12:34:57Z",
"source": "modal",
"ipAddress": "192.168.1.10",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
},
{
"vendorId": 101,
"consentGiven": true,
"timestamp": "2024-09-15T12:34:58Z",
"source": "modal",
"ipAddress": "192.168.1.10",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
}
],
"globalConsent": true,
"consentVersion": "2.0"
}
The response includes a consentId and a cookie to persist the user’s state.
{
"consentId": "c_7a3b2c",
"timestamp": "2024-09-15T12:34:59Z",
"cookie": {
"name": "consent_state",
"value": "c_7a3b2c",
"domain": ".example.com",
"path": "/",
"expires": "2025-03-15T12:34:59Z",
"httpOnly": true,
"secure": true
}
}
A CMP stores consent state in a database, typically with a schema that supports multiple consent versions and granular tracking per user, purpose, vendor, and legal basis.
CREATE TABLE consent_states (
id UUID PRIMARY KEY,
user_id UUID NOT NULL,
session_id UUID,
consent_version TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL,
updated_at TIMESTAMPTZ NOT NULL,
metadata JSONB,
UNIQUE(user_id, consent_version)
);
CREATE TABLE user_consent (
id UUID PRIMARY KEY,
consent_id UUID REFERENCES consent_states(id),
purpose_id INT,
vendor_id INT,
consent_given BOOLEAN NOT NULL,
expiration TIMESTAMPTZ,
source TEXT,
ip_address INET,
user_agent TEXT,
timestamp TIMESTAMPTZ NOT NULL,
FOREIGN KEY (consent_id) REFERENCES consent_states(id)
);
The consent_version field enables backward compatibility. A new consent form may introduce new purposes, but the system must support legacy versions. When a user returns, the CMP checks for an existing consent_state cookie. If not present, it loads the most recent consent version and prompts the user.
Once consent is captured, it must be enforced across services. The most common method is a consent header or cookie passed with every request.
GET /api/user-profile HTTP/1.1
Host: api.example.com
Cookie: consent_state=c_7a3b2c
Authorization: Bearer xyz987
Accept: application/json
The backend service checks the consent_state cookie and queries the consent database to determine which data may be used.
func getConsentsForUser(userID uuid.UUID, consentVersion string) ([]Consent, error) {
query := `
SELECT c.id, c.purpose_id, c.vendor_id, c.consent_given, c.expiration
FROM user_consent c
JOIN consent_states cs ON cs.id = c.consent_id
WHERE cs.user_id = $1 AND cs.consent_version = $2
ORDER BY c.timestamp DESC
`
rows, err := db.Query(query, userID, consentVersion)
if err != nil {
return nil, err
}
defer rows.Close()
var consents []Consent
for rows.Next() {
var consent Consent
if err := rows.Scan(&consent.ID, &consent.PurposeID, &consent.VendorID, &consent.ConsentGiven, &consent.Expiration); err != nil {
return nil, err
}
consents = append(consents, consent)
}
return consents, nil
}
The service uses the result to filter data access, enable tracking, and log consent usage.
Users may update their consent after initial capture. The CMP must support a PATCH /api/consents/{consentId} endpoint to modify existing choices.
PATCH /api/consents/c_7a3b2c HTTP/1.1
Host: cmp.example.com
Content-Type: application/json
Authorization: Bearer abc123
{
"consents": [
{
"purposeId": 2,
"consentGiven": false,
"timestamp": "2024-09-16T10:00:00Z"
},
{
"vendorId": 103,
"consentGiven": true,
"expiration": "2025-09-15T10:00:00Z"
}
],
"globalConsent": true,
"consentVersion": "2.1"
}
The update triggers a consent_updated event. A background job processes this event, updating the database and publishing to a message queue.
{
"event": "consent_updated",
"consentId": "c_7a3b2c",
"userId": "u_456789",
"timestamp": "2024-09-16T10:00:01Z",
"version": "2.1",
"changes": [
{
"type": "purpose",
"purposeId": 2,
"oldValue": true,
"newValue": false,
"timestamp": "2024-09-16T10:00:00Z"
},
{
"type": "vendor",
"vendorId": 103,
"oldValue": false,
"newValue": true,
"expiration": "2025-09-15T10:00:00Z",
"timestamp": "2024-09-16T10:00:00Z"
}
]
}
This event is consumed by downstream services: analytics pipelines, marketing tools, and audit systems.
The CMP integrates with other compliance systems through standard APIs and message formats.
Every consent decision is logged for audit purposes. The audit trail includes:
The audit log is stored in a consent_audit table:
CREATE TABLE consent_audit (
id UUID PRIMARY KEY,
consent_id UUID REFERENCES consent_states(id),
action TEXT NOT NULL, -- "created", "updated", "revoked"
actor_type TEXT, -- "user", "system", "api"
actor_id UUID,
old_state JSONB,
new_state JSONB,
metadata JSONB,
timestamp TIMESTAMPTZ NOT NULL
);
A GET /api/consents/{consentId}/audit endpoint returns a paginated list of audit events.
GET /api/consents/c_7a3b2c/audit?limit=10&page=1 HTTP/1.1
Host: cmp.example.com
Authorization: Bearer abc123
{
"data": [
{
"id": "a_123abc",
"consentId": "c_7a3b2c",
"action": "created",
"actorType": "user",
"actorId": "u_456789",
"oldState": null,
"newState": {
"userId": "u_456789",
"consentVersion": "2.0",
"consents": [
{ "purposeId": 1, "consentGiven": true },
{ "purposeId": 2, "consentGiven": true },
{ "vendorId": 101, "consentGiven": true }
]
},
"metadata": {
"source": "modal",
"ipAddress": "192.168.1.10",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
},
"timestamp": "2024-09-15T12:34:59Z"
},
{
"id": "a_456def",
"consentId": "c_7a3b2c",
"action": "updated",
"actorType": "user",
"actorId": "u_456789",
"oldState": {
"consents": [
{ "purposeId": 2, "consentGiven": true }
]
},
"newState": {
"consents": [
{ "purposeId": 2, "consentGiven": false }
]
},
"metadata": {
"source": "modal",
"ipAddress": "192.168.1.10",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)"
},
"timestamp": "2024-09-16T10:00:01Z"
}
],
"pagination": {
"currentPage": 1,
"totalPages": 3,
"totalItems": 24,
"limit": 10
}
}
Consents may expire. A background job runs every 12 hours to check for expired consents.
0 */12 * * * root /opt/cmp/bin/check-expired-consents --dry-run --log-level=info
The job queries the database for all consents where expiration < NOW() and marks them as expired.
UPDATE consent_states
SET expired_at = NOW(), status = 'expired'
WHERE id IN (
SELECT id
FROM consent_states
WHERE expiration < NOW()
AND status != 'expired'
);
The system also supports explicit revocation. A POST /api/consents/{consentId}/revoke endpoint triggers a revocation event.
POST /api/consents/c_7a3b2c/revoke HTTP/1.1
Host: cmp.example.com
Authorization: Bearer abc123
Content-Type: application/json
{
"reason": "User clicked 'Revoke All' in settings",
"timestamp": "2024-09-16T11:30:00Z"
}
This generates an audit entry and notifies dependent services via a message queue.
consent_state cookie. The backend assumes all data is unconsented, leading to inconsistent tracking. The root cause is often a misconfigured cookie domain or a script that fails to set the cookie on consent capture.consentVersion is not persisted, leading to partial or stale consent states.TIMESTAMP WITH TIME ZONE, but the client sends Z-padded ISO 8601 strings. A common failure is missing the Z in the timestamp, causing the backend to interpret it as local time and misaligning consent events.PATCH requests. A race condition can lead to one update being overwritten by the other. The solution is to use version or etag fields in the request body.PATCH /api/consents/c_7a3b2c HTTP/1.1
If-Match: "v_2.1"
Content-Type: application/json
{
"consents": [ ... ],
"consentVersion": "2.1",
"version": 4
}
The server checks the version number and returns 412 Precondition Failed if the version has changed.
expiration field. Data from a user who revoked consent is still processed, leading to non-compliant data usage.consent_audit table grows without indexing on consent_id and timestamp. Queries on the audit trail become slow after 100k events. The fix is to add a composite index:CREATE INDEX idx_consent_audit_consents ON consent_audit(consent_id, timestamp);
These edge cases, often discovered only after a consent-related audit or a user complaint, highlight the importance of treating consent not as a one-time setup but as a continuous, system-wide process.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy