Consent Log Implementation — User Consent Journal

During a GDPR compliance audit of a client portal, we identified the absence of an immutable consent log. The regulator requested proof—without a Consent Log the company risked a fine of up to €20 million or 4% of annual turnover. A typical request: provide the history of consent changes for each us

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1419
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1287
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    983
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1245
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    983
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    998

During a GDPR compliance audit of a client portal, we identified the absence of an immutable consent log. The regulator requested proof—without a Consent Log the company risked a fine of up to €20 million or 4% of annual turnover. A typical request: provide the history of consent changes for each user over the last 3 years. Without a log, you cannot do that—you'd either have to admit data absence or forge records. Court practice confirms that companies without a Consent Log lose cases. We developed a solution that not only records every user action but also provides an API for management and data export. Our experience: over 30 projects where the consent log became the foundation of legal security. According to GDPR (https://en.wikipedia.org/wiki/General_Data_Protection_Regulation), consent must be obtained lawfully and demonstrably. Consent Log is the only way to ensure transparent auditing. Regulators issue billions of euros in fines annually for such violations.

What should a Consent Log contain?

GDPR requires recording the context of each event:

  • Timestamp of consent
  • Who gave consent (user or anonymous ID)
  • What exactly was consented to (specific categories: analytics, marketing, etc.)
  • Document version the user reviewed
  • Method of obtaining consent (banner, checkbox, API)
  • IP address and user-agent for jurisdiction binding
Characteristic Without Log With Consent Log
Ability to prove consent obtained No Yes, down to the second
Response time for DSAR 5–7 days of manual collection 5 minutes via API
Cost of processing one DSAR request up to €2000 negligible
Fine risk for lack of proof High (up to €20M) Minimized

Database schema

CREATE TABLE consent_events ( id BIGSERIAL PRIMARY KEY, -- Идентификация user_id BIGINT REFERENCES users(id) ON DELETE SET NULL, anonymous_id UUID, -- для неавторизованных session_id VARCHAR(100), -- Данные согласия event_type VARCHAR(20) NOT NULL, -- 'granted', 'denied', 'withdrawn', 'updated' categories JSONB NOT NULL, -- {"analytics": true, "marketing": false, ...} document_version VARCHAR(20), -- версия Privacy Policy method VARCHAR(30), -- 'banner', 'settings_page', 'api', 'import' -- Контекст ip_address INET, user_agent TEXT, country_code CHAR(2), language_code CHAR(5), -- Аудит created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Запрет обновления строк (immutable audit log) updated_at TIMESTAMPTZ, CONSTRAINT no_updates CHECK (updated_at IS NULL) ); -- Индексы для быстрого поиска CREATE INDEX idx_consent_user ON consent_events(user_id) WHERE user_id IS NOT NULL; CREATE INDEX idx_consent_anon ON consent_events(anonymous_id) WHERE anonymous_id IS NOT NULL; CREATE INDEX idx_consent_date ON consent_events(created_at); CREATE INDEX idx_consent_type ON consent_events(event_type); 

How to implement an immutable Consent Log?

We use a combination of SQL constraints and architecture that prevents record updates. Additionally, we can add a hash of the previous row for a chain—a blockchain approach without blockchain. Result: confidence that the log has not been compromised. For fast searches—indexes on user_id and anonymous_id. The entire code is covered by immutability tests.

Event: a user gives consent for analytics and marketing via a banner. The table gets: timestamp, user_id (or anonymous_id), categories {"analytics": true, "marketing": true}, version "v2.1", method "banner", IP from Germany, user-agent Chrome on Windows. All rows are only inserted, never updated.

Recording consents

import uuid from datetime import datetime import hashlib class ConsentLogger: def __init__(self, db, geoip): self.db = db self.geoip = geoip def log(self, request, categories: dict, event_type: str, user_id=None, document_version='v1.0'): # Определить анонимный идентификатор anonymous_id = self._get_or_create_anonymous_id(request) country = self.geoip.country(request.remote_addr) self.db.execute(""" INSERT INTO consent_events (user_id, anonymous_id, session_id, event_type, categories, document_version, method, ip_address, user_agent, country_code, created_at) VALUES (%s, %s, %s, %s, %s::jsonb, %s, %s, %s, %s, %s, %s) """, ( user_id, anonymous_id, request.session.get('id'), event_type, json.dumps(categories), document_version, 'banner', request.remote_addr, request.user_agent.string[:500], country, datetime.utcnow() )) def _get_or_create_anonymous_id(self, request): cookie_id = request.cookies.get('consent_id') if cookie_id: return cookie_id return str(uuid.uuid4()) def get_user_consent_history(self, user_id: int): return self.db.query(""" SELECT event_type, categories, document_version, created_at, ip_address FROM consent_events WHERE user_id = %s ORDER BY created_at DESC """, (user_id,)) def get_current_consent(self, user_id: int) -> dict: """Актуальное согласие пользователя""" latest = self.db.query_one(""" SELECT categories FROM consent_events WHERE user_id = %s AND event_type IN ('granted', 'updated') ORDER BY created_at DESC LIMIT 1 """, (user_id,)) return latest['categories'] if latest else {} 

API for consent management

@app.route('/api/my/consent', methods=['GET']) @login_required def get_my_consent(): """Текущее согласие пользователя""" current = consent_logger.get_current_consent(current_user.id) history = consent_logger.get_user_consent_history(current_user.id) return jsonify({ 'current': current, 'history': [{ 'event': r['event_type'], 'categories': r['categories'], 'version': r['document_version'], 'date': r['created_at'].isoformat(), } for r in history[:10]] }) @app.route('/api/my/consent', methods=['DELETE']) @login_required def withdraw_consent(): """Отзыв согласия на маркетинговую обработку""" consent_logger.log( request, categories={'analytics': False, 'marketing': False, 'preferences': False}, event_type='withdrawn', user_id=current_user.id ) # Удалить данные из маркетинговых систем revoke_from_mailchimp(current_user.email) revoke_from_facebook_custom_audience(current_user.email) return jsonify({'status': 'withdrawn'}) 

Export for regulator

def export_consent_for_user(user_id: int) -> dict: """Отчёт для ответа на запрос регулятора или DSAR""" records = db.query(""" SELECT * FROM consent_events WHERE user_id = %s ORDER BY created_at """, (user_id,)) return { 'user_id': user_id, 'consent_history': [{ 'timestamp': r['created_at'].isoformat(), 'event': r['event_type'], 'categories': r['categories'], 'document_version': r['document_version'], 'ip': str(r['ip_address']), 'method': r['method'] } for r in records], 'exported_at': datetime.utcnow().isoformat(), 'format_version': '1.0' } 

API export reduces DSAR response time from 5 days to 5 minutes—1440 times faster than manual collection.

Implementation process and timeline

  1. Analysis—we study the current consent system, identify gaps.
  2. Design—database schema, API, export formats.
  3. Implementation—write code, migrations, set up indexes.
  4. Testing—unit tests, integration immutability testing, load up to 1000 req/s.
  5. Deployment—deploy to production, team consultation.

Turnkey implementation—2 to 3 working days. Integration with complex legacy systems may extend to 5 days. We have completed over 30 consent log projects in 6 years of work—accumulated experience helps avoid typical mistakes.

What is included in the work

  • Consent Logger module code in Python/Django or Node.js
  • SQL migrations to create the consent_events table and indexes
  • REST API for consent management (current, withdrawal, history)
  • Data export for DSAR in JSON format, ready to send to regulator
  • Instructions for frontend integration (consent banner, settings page)
  • Load testing: up to 1000 requests per second
  • Legal consultation (which categories to record, retention period—typically 3 years)
Stage What we do Result
Analysis Audit current consent flow, find vulnerabilities Report with recommendations
Design Schema consent_events, indexes, API architecture Schema documentation
Implementation Write ConsentLogger, REST endpoints Working code
Testing Check immutability, load testing 100% test pass
Deployment Deploy, monitor Running on production

Typical mistakes when implementing Consent Log

  • Storing only current consent, without history of changes—this does not comply with GDPR.
  • Using UPDATE instead of INSERT—the log becomes rewriteable.
  • Lack of anonymous_id for unauthorized users—impossible to tie events.
  • Missing contextual data (IP, user-agent, language)—regulator may demand it.
  • DSAR export not configured—must be written on the fly when requested.

We guarantee that your Consent Log implementation will comply with the spirit and letter of GDPR. Contact us for a free assessment of your project. Order the implementation – get a ready solution in a couple of days.