GDPR-compliant consent management with audit trail
Double opt-in, withdrawal handling and an audit log fit for the Austrian DPA
Many platforms store consent as a single boolean column — `marketing_opt_in = true`. That predates the GDPR and won't survive a complaint today: the Austrian DPA requires consent to be demonstrable in detail (exact wording, date, channel) — and withdrawal must take effect within 30 days, be documented and confirmed back to the data subject. Without that, you risk formal warnings; repeat findings draw fines of up to 4% of global annual turnover or EUR 20 m.
The Austrian Data Protection Act (DSG, BGBl. I 165/1999) supplements the GDPR with national specifics — the Datenschutzbehörde (DPA) in Vienna publishes annual guidance interpretations. For notifications this means: consent must be informed, freely given, specific to a purpose and demonstrable, and withdrawal must be as easy as granting it. 4notify implements this as a consent ledger: every opt-in is stored with timestamp, IP, user-agent, the exact wording the user saw and the channel scope. The ledger is append-only and exportable as CSV for DPA requests.
| Channel | Primary provider | Fallback |
|---|---|---|
| Consent-Form | Hosted form @ 4notify | Embed (JS widget) |
| Double-Opt-in E-Mail | 4notify Mail (DKIM/DMARC) | Mailgun EU |
| Audit-Ledger | Postgres append-only | S3 EU-Central WORM |
| Widerruf-Link | Signed HMAC-token URL | Inbound STOP keyword |
| DSB-Export | Async CSV job | JSON API (Pagination) |
Processing is lawful only if the data subject has given consent. For SMS/email direct marketing, consent is the only viable legal basis — Art. 6(1)(f) legitimate interest rarely survives a marketing-channel balancing test.
Where processing is based on consent, the controller must be able to demonstrate consent. The proof must be reconstructible by content and date — a boolean flag isn't enough.
Information duty: at collection time, identity of controller, purposes, legal basis, recipients, storage duration and data-subject rights must be disclosed. The exact wording shown is archived in the consent ledger.
The Austrian DSG raises data protection to constitutional rank — the DPA tends to interpret it more strictly than other EU supervisory authorities, particularly the granularity requirement for bundled consents.
Lock the consent wording in the ledger
Before consent is collected in production, the exact wording is locked as a versioned template in the ledger. Every later opt-in references this version — even when the frontend changes later, the historical proof stays consistent.
Double opt-in via email with a signed token
After the first click, an HMAC-signed confirmation link is sent to the supplied email address. Only after the user clicks does the ledger flip the consent to `active`. The token is valid for 72 hours — afterwards consent silently lapses.
Ledger as an append-only table
Instead of UPDATEs, every state change is written as a new row (granted, confirmed, withdrawn, expired). The full history stays forensically reconstructible — essential when DPA complaints look back years.
Withdrawal via link + SMS STOP + API
Every send carries a signed withdrawal link in the footer (email) or a STOP hint (SMS). Inbound STOP messages are delivered back by the gateway as MO webhooks and simultaneously written to the ledger as a new row.
DPA export as an async CSV job
On DPA request, the full history for a data subject is exported as CSV (with timestamp, IP, user-agent, text version, channel scope). The job runs async, generates a signed S3 URL and stays retrievable for 7 days.
Test procedure: (1) Create an entry with a test email — verify the ledger row was written with status `pending`. (2) Receive the confirmation email, click the link — confirm a new row with status `confirmed` and the same consent_id exists. (3) Click the withdrawal link — new row with `withdrawn`. (4) Send a test SMS to a test number, reply STOP, verify the MO handler inserts a `withdrawn` row with channel `sms`. (5) Call the DPA-export endpoint, download the CSV, verify all four rows with correct timestamps.
bash curl -X POST https://api.4notify.net/v1/consent/grant \ -H "Authorization: Bearer $API_KEY' \ -H "Content-Type: application/json' \ -d '{ "email': '[email protected]", "phone': '+436641234567", "wording_id': 'consent_v3_at", "purposes': ['transactional", "appointment_reminder"], "channels': ['sms", "email"], "client_ip': '85.127.0.1", "user_agent': 'Mozilla/5.0 ...", "consent_type': 'double_opt_in" }'
WienerHandwerk: confirm your newsletter sign-up by replying YES to this number. Reply STOP to opt out. Privacy: 4notify.net/datenschutz
- Lock the consent wording as a versioned template (e.g. consent_v3_at)
- Double opt-in email signed with DKIM and DMARC
- Ledger table set up as append-only (Postgres trigger or application layer)
- Withdrawal link signed with HMAC, valid for 30 days
- Inbound STOP webhook active for every SMS sender ID
- DPA-export endpoint with async job + signed S3 URL implemented
- Retention policy: audit log kept for at least 7 years, auto-deleted afterwards
Instead of a boolean `opt_in = true` flag, 4notify maintains an append-only audit ledger — every consent, confirmation and withdrawal stays forensically reconstructible, years back.
How does the Austrian DPA interpretation differ from Germany's BfDI?
The DPA is stricter on granularity: bundled consents (e.g. ’marketing + profiling + third-party sharing’ in one checkbox) are consistently treated as invalid, where the German BfDI sometimes allows leeway. One checkbox per purpose is the safe choice.
Does withdrawal really need to take effect in 30 days?
GDPR Art. 12(3) requires a response to data-subject rights within 30 days. For a pure withdrawal of consent the DPA expects effectively immediate effect — the 30-day window is meant for access and erasure requests that require effort.
What happens if a customer orders again after withdrawing consent?
Transactional messages (order confirmation, shipment status) stay covered by legitimate interest Art. 6(1)(f) — withdrawal applies only to marketing purposes. The ledger separates `transactional` and `marketing` as distinct purpose_ids.
Open a free account
14 days, no card. German-language support.