2026-04-18·16 min read·

GDPR Art.33-34: Breach Notification — 72h SA Reporting, Data Subject Communication & Breach Register Design (2026)

Post #428 in the sota.io EU Cyber Compliance Series

A personal data breach is not automatically a GDPR violation. But failing to notify the right parties — in the right way, within the right timeframe — is. GDPR Articles 33 and 34 create a two-track notification system: Art.33 governs notification to the supervisory authority (SA), Art.34 governs direct communication to affected data subjects. The obligations are asymmetric: SA notification applies to almost all breaches (with a narrow "no risk" exception); data subject notification applies only when the breach is "likely to result in high risk."

Engineers often learn breach response through incidents rather than design. This post reverses that sequence — building the systems and knowledge before the incident happens.


GDPR Chapter IV: Art.33-34 in Context

ArticleObligationThresholdTimeframe
Art.32Security of processing — TOMsAll processingOngoing
Art.30Records of processing (RoPA)All controllersOngoing
Art.33SA notificationBreach + no "no risk" exception72 hours
Art.34Data subject communicationBreach + likely high riskWithout undue delay
Art.58(1)(f)SA investigative powersPost-breach auditSA discretion
Art.83(4)Fines for Art.33/34 violationsNon-complianceUp to €10M or 2%

Art.33 and Art.34 are consequence-management obligations — they activate after a breach occurs. They sit downstream of Art.32 (prevention) and upstream of Art.58 (SA investigation). The practical implication: your security controls reduce breach probability; your Art.33-34 procedures reduce the legal and reputational consequence of breaches that do occur.


What Is a Personal Data Breach?

Art.4(12) defines a personal data breach as:

"a breach of security leading to the accidental or unlawful destruction, loss, alteration, unauthorised disclosure of, or access to, personal data transmitted, stored or otherwise processed"

Three categories:

CategoryExamples
Confidentiality breachUnauthorised access: SQL injection exposing user emails, cloud storage bucket misconfiguration, accidental email CC
Integrity breachUnauthorised alteration: malicious modification of user records, database corruption
Availability breachAccidental/unlawful destruction or loss: ransomware encrypting data, deletion without backup

Engineering note: A breach does not require exfiltration. A ransomware attack that only encrypts data (availability breach) is still a personal data breach under Art.4(12). A misconfigured S3 bucket that is publicly accessible — even if no one accessed it — is a breach at the moment of misconfiguration if personal data was in scope.


Art.33: Notification to the Supervisory Authority

The 72-Hour Window

Art.33(1) states:

"In the case of a personal data breach, the controller shall without undue delay and, where feasible, not later than 72 hours after having become aware of it, notify the personal data breach to the supervisory authority competent in accordance with Article 55, unless the personal data breach is unlikely to result in a risk to the rights and freedoms of natural persons."

Three elements to parse:

1. "Becomes aware" — The clock starts when the controller has a reasonable degree of certainty that a breach has occurred. EDPB Guidelines 9/2022 §7: "a controller should be considered as 'aware' when that controller has a reasonable degree of certainty that a security incident has occurred that has led to personal data being compromised."

This is not the moment of detection, not the moment of confirmation. It is the moment of reasonable certainty. A security alert at 09:00 that an engineer investigates and confirms as a breach at 14:00 — the controller becomes aware at 14:00. A breach discovered by a processor at 09:00 but reported to the controller at 16:00 — the controller becomes aware at 16:00 (though the processor has parallel obligations under Art.33(2)).

2. "Where feasible" — The 72-hour deadline is a qualified obligation. If notification within 72 hours is not feasible (complex multi-system breach, insufficient information), the controller must:

This is the phased notification mechanism — use it rather than missing the 72-hour window entirely.

3. "Unlikely to result in a risk" — The narrow exception. If the breach is genuinely unlikely to cause risk to individuals (e.g., loss of a strongly encrypted device where keys are not compromised), no SA notification is required. The controller must still document this assessment and the rationale under Art.33(5).

Art.33(2): Processor Obligations

If you are a processor, Art.33(2) requires you to notify the controller "without undue delay" upon becoming aware of a breach — without specifying 72 hours. The rationale: the controller's 72-hour clock runs from when the controller becomes aware; processor delay directly consumes the controller's notification window.

Engineering implication: Processor SLAs for breach reporting to controllers should be contractually fixed at ≤24 hours. A processor who takes 48 hours to notify the controller leaves the controller with only 24 hours to assess, document, and notify the SA.

Art.33(3): Notification Content

The SA notification must contain at minimum:

(a) nature of the breach including categories/approximate number of data subjects 
    and records concerned
(b) name and contact details of the DPO or contact point
(c) likely consequences of the breach
(d) measures taken or proposed to address the breach, including to mitigate 
    possible adverse effects

Note: Art.33(3) explicitly states that information may be "provided in phases" where not all information is available at the time of initial notification. This prevents organisations from delaying notification until they have complete information.


Art.34: Communication to Data Subjects

The High-Risk Threshold

Art.34(1) requires communication to data subjects "without undue delay" when the breach is likely to result in a high risk to the rights and freedoms of natural persons.

The threshold is deliberately higher than Art.33's "risk" standard — it requires "high risk." EDPB Guidelines 9/2022 §87 distinguishes:

Risk LevelArt.33Art.34
No riskNo SA notification (Art.33(1) exception)No data subject notification
RiskSA notification requiredNo data subject notification
High riskSA notification requiredData subject notification required

What Constitutes "High Risk"?

EDPB Guidelines 9/2022 §88 identifies factors:

Nature of data:

Volume and scope:

Nature of breach:

Vulnerability of data subjects:

Practical assessment matrix:

ScenarioHigh Risk?Rationale
Unencrypted backup drive lost — contains user emails and password hashes (bcrypt)Likely YESEmails exposed; password hashes crackable given sufficient compute
Encrypted drive lost — AES-256 — no evidence attacker has keyLikely NOArt.34(3)(a) encryption exception
S3 bucket public 2 hours — access logs show no external GET requestsUncertainArt.34(3)(b) applies if controller took immediate remediation action
SQL injection — attacker downloaded 50k user records including email, name, IPYESVolume + malicious actor + email enumeration enables targeted phishing
Accidental email CC — 3 users received one other user's email addressNO (typically)Limited scope, low sensitivity
Healthcare app — 200 patient records accessed by unauthorised employeeYESSpecial category (health) + trust relationship breach

Art.34(3): Exceptions to Data Subject Notification

Art.34 provides three conditions under which data subject notification is not required even for high-risk breaches:

(a) Encryption exception: The controller has implemented appropriate technical protection measures — specifically encryption — rendering the data unintelligible to unauthorised persons. If exfiltrated data is properly encrypted with a key the attacker does not have, no data subject notification is needed.

(b) Remediation exception: The controller has taken subsequent measures that ensure the high risk is no longer likely to materialise. This is narrow — it requires that the risk is genuinely mitigated, not merely reduced.

(c) Disproportionate effort exception: Communication to each data subject would involve disproportionate effort. In this case, a public communication or similar measure must be made instead. This applies to large-scale breaches where individual notification is impractical.

Note: Even when Art.34(3) exceptions apply, the underlying breach still triggers Art.33 SA notification (assuming the "risk" threshold is met). The exceptions only eliminate the data subject communication obligation, not the SA notification obligation.

Art.34(2): Content of Data Subject Communication

The communication must:

Engineering note: The content requirements mirror Art.33(3) but without the "approximate number of records" field. The communication must be written for the data subject — plain language, not legal boilerplate.


Art.33(5): The Breach Register

Art.33(5) requires controllers to document all personal data breaches — including those not notified to the SA — enabling the SA to verify compliance with Art.33 and Art.34:

"The controller shall document any personal data breaches, comprising the facts relating to the personal data breach, its effects and the remedial action taken."

This is the breach register — a mandatory compliance artefact separate from the RoPA. Unlike the RoPA, the breach register must capture:

FieldDescription
Breach date/timeWhen the breach occurred (or is estimated to have occurred)
Discovery date/timeWhen the controller became aware
Nature and categoryConfidentiality/integrity/availability; type of data
Data subjects affectedCategories and approximate number
Records affectedCategories and approximate number
Risk assessmentRisk level determination with rationale
Art.33 decisionNotified / Not notified + rationale
Art.34 decisionNotified / Not notified + rationale
SA notificationDate, reference number, SA contacted
Data subject notificationDate, method, content summary
RemediationActions taken and completed
Lessons learnedChanges to prevent recurrence

Retention: EDPB recommends retaining breach register entries for a minimum of 3 years, though some SAs recommend longer (5 years for breaches that result in SA investigation).


Risk Assessment Methodology

A structured risk assessment prevents both under-notification (missing genuinely high-risk breaches) and over-notification (flooding the SA with low-risk incidents).

ENISA Breach Risk Assessment

ENISA's methodology (used in EDPB guidance) evaluates four factors:

1. Context of the breach — Who is likely affected? Vulnerable groups (children, patients) elevate risk.

2. Ease of identification — Can the attacker identify individuals from the exposed data? Direct identifiers (name + email) score higher than indirect identifiers.

3. Nature of the data — Special category (Art.9), financial, credential, or location data scores highest.

4. Consequences — What harm can result? Physical harm, financial loss, discrimination, reputational damage, loss of control over personal data.

Practical Risk Matrix

                    Sensitivity (Low → High)
                 Low      Medium     High     Critical
Volume  Single    No risk  No risk    Risk     High risk
        <100      No risk  Risk       Risk     High risk  
        <10,000   Risk     Risk       High     High risk
        >10,000   Risk     High risk  High     High risk

Critical sensitivity: Passwords (plain text), encryption keys, biometric data, financial credentials High sensitivity: Health data, special category Art.9, national IDs, financial data Medium sensitivity: Email + name, IP addresses, usage data Low sensitivity: Aggregated/anonymised data (not personal data, technically)


Phased Notification in Practice

The 72-hour window is tight. For complex breaches, use phased notification:

Hour 0-4: Initial Assessment

Hour 4-24: Containment

Hour 24-72: Initial SA Notification

Hour 72+: Subsequent Notifications

Engineering implementation: Build a notification workflow with explicit state transitions (ASSESSING → CONTAINED → NOTIFIED_SA → NOTIFIED_SUBJECTS → CLOSED) to prevent notification steps from being missed in incident chaos.


Python Implementation

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
import json


class BreachCategory(str, Enum):
    CONFIDENTIALITY = "confidentiality"  # unauthorised disclosure/access
    INTEGRITY = "integrity"              # unauthorised alteration
    AVAILABILITY = "availability"        # destruction/loss


class RiskLevel(str, Enum):
    NO_RISK = "no_risk"
    RISK = "risk"
    HIGH_RISK = "high_risk"


class NotificationStatus(str, Enum):
    NOT_REQUIRED = "not_required"
    PENDING = "pending"
    SUBMITTED = "submitted"
    SUPPLEMENTARY_PENDING = "supplementary_pending"


@dataclass
class BreachRecord:
    breach_id: str
    occurred_at: Optional[datetime]       # when breach happened
    discovered_at: datetime                # when controller became aware
    categories: list[BreachCategory]
    data_subject_categories: list[str]     # "registered users", "employees"
    data_categories: list[str]             # "email", "hashed password", "IP"
    subjects_affected_estimate: int
    records_affected_estimate: int
    is_special_category: bool = False      # Art.9/10 data involved
    malicious_actor: bool = False          # active attacker vs. accidental
    data_encrypted_at_rest: bool = False
    encryption_key_compromised: bool = False

    # Populated during assessment
    risk_level: Optional[RiskLevel] = None
    risk_rationale: str = ""

    # Art.33 SA notification
    sa_notification_status: NotificationStatus = NotificationStatus.PENDING
    sa_notified_at: Optional[datetime] = None
    sa_reference: str = ""
    sa_not_notified_reason: str = ""

    # Art.34 data subject notification
    ds_notification_required: bool = False
    ds_notification_status: NotificationStatus = NotificationStatus.PENDING
    ds_notified_at: Optional[datetime] = None
    ds_notification_method: str = ""
    ds_art34_3_exception: str = ""         # which exception applies

    # Remediation
    remediation_actions: list[str] = field(default_factory=list)
    lessons_learned: str = ""
    closed_at: Optional[datetime] = None

    @property
    def sa_deadline(self) -> datetime:
        return self.discovered_at + timedelta(hours=72)

    @property
    def hours_to_sa_deadline(self) -> float:
        remaining = self.sa_deadline - datetime.utcnow()
        return remaining.total_seconds() / 3600

    @property
    def sa_deadline_breached(self) -> bool:
        return (
            datetime.utcnow() > self.sa_deadline
            and self.sa_notification_status == NotificationStatus.PENDING
        )

    def assess_risk(self) -> RiskLevel:
        """ENISA-aligned risk assessment."""
        score = 0

        # Data sensitivity
        if self.is_special_category:
            score += 4
        elif any(c in self.data_categories for c in ["password", "national_id", "biometric", "financial"]):
            score += 3
        elif any(c in self.data_categories for c in ["email", "name", "ip_address"]):
            score += 1

        # Volume
        if self.subjects_affected_estimate >= 10000:
            score += 3
        elif self.subjects_affected_estimate >= 100:
            score += 2
        elif self.subjects_affected_estimate >= 1:
            score += 1

        # Malicious actor
        if self.malicious_actor:
            score += 2

        # Breach category
        if BreachCategory.CONFIDENTIALITY in self.categories:
            score += 1

        # Encryption mitigant
        if self.data_encrypted_at_rest and not self.encryption_key_compromised:
            score = max(0, score - 3)

        if score == 0:
            self.risk_level = RiskLevel.NO_RISK
        elif score <= 3:
            self.risk_level = RiskLevel.RISK
        else:
            self.risk_level = RiskLevel.HIGH_RISK

        return self.risk_level

    def assess_art34_requirement(self) -> bool:
        """Determine if data subject notification is required."""
        if self.risk_level != RiskLevel.HIGH_RISK:
            self.ds_notification_required = False
            return False

        # Check Art.34(3) exceptions
        if self.data_encrypted_at_rest and not self.encryption_key_compromised:
            self.ds_art34_3_exception = "art34_3a_encryption"
            self.ds_notification_required = False
            return False

        self.ds_notification_required = True
        return True

    def validate(self) -> list[str]:
        """Return list of compliance issues."""
        issues = []

        if self.risk_level is None:
            issues.append("Risk assessment not performed — call assess_risk() first")

        if self.sa_deadline_breached:
            issues.append(
                f"Art.33 72h deadline breached: {self.hours_to_sa_deadline:.1f}h overdue"
            )

        if (
            self.risk_level != RiskLevel.NO_RISK
            and self.sa_notification_status == NotificationStatus.PENDING
            and self.hours_to_sa_deadline < 12
        ):
            issues.append(
                f"Art.33 SA notification urgent: {self.hours_to_sa_deadline:.1f}h remaining"
            )

        if (
            self.ds_notification_required
            and self.ds_notification_status == NotificationStatus.PENDING
            and not self.ds_art34_3_exception
        ):
            issues.append("Art.34 data subject notification pending — communicate without undue delay")

        return issues

    def to_register_entry(self) -> dict:
        return {
            "breach_id": self.breach_id,
            "occurred_at": self.occurred_at.isoformat() if self.occurred_at else "unknown",
            "discovered_at": self.discovered_at.isoformat(),
            "sa_deadline": self.sa_deadline.isoformat(),
            "categories": [c.value for c in self.categories],
            "subjects_affected": self.subjects_affected_estimate,
            "risk_level": self.risk_level.value if self.risk_level else "not_assessed",
            "sa_status": self.sa_notification_status.value,
            "sa_reference": self.sa_reference,
            "ds_notification_required": self.ds_notification_required,
            "ds_status": self.ds_notification_status.value,
            "remediation": self.remediation_actions,
            "closed": self.closed_at.isoformat() if self.closed_at else None,
        }


@dataclass
class BreachRegister:
    """Art.33(5) breach register implementation."""
    organisation: str
    entries: list[BreachRecord] = field(default_factory=list)

    def add_breach(self, breach: BreachRecord) -> None:
        breach.assess_risk()
        breach.assess_art34_requirement()
        self.entries.append(breach)

    def get_open_breaches(self) -> list[BreachRecord]:
        return [b for b in self.entries if b.closed_at is None]

    def get_overdue_notifications(self) -> list[BreachRecord]:
        return [b for b in self.entries if b.sa_deadline_breached]

    def get_pending_ds_notifications(self) -> list[BreachRecord]:
        return [
            b for b in self.entries
            if b.ds_notification_required
            and b.ds_notification_status == NotificationStatus.PENDING
        ]

    def get_critical_alerts(self) -> list[tuple[BreachRecord, list[str]]]:
        alerts = []
        for breach in self.get_open_breaches():
            issues = breach.validate()
            if issues:
                alerts.append((breach, issues))
        return alerts

    def export_for_sa(self) -> list[dict]:
        """Export breach register in SA-inspectable format."""
        return [b.to_register_entry() for b in self.entries]

Usage example — SQL injection incident:

breach = BreachRecord(
    breach_id="BREACH-2026-001",
    occurred_at=datetime(2026, 4, 15, 3, 42),    # detected in logs
    discovered_at=datetime(2026, 4, 15, 9, 15),   # engineer confirmed at 09:15
    categories=[BreachCategory.CONFIDENTIALITY],
    data_subject_categories=["registered_users"],
    data_categories=["email", "hashed_password", "ip_address", "name"],
    subjects_affected_estimate=47_000,
    records_affected_estimate=47_000,
    is_special_category=False,
    malicious_actor=True,
    data_encrypted_at_rest=False,
)

# SA deadline: 2026-04-15 09:15 + 72h = 2026-04-18 09:15
risk = breach.assess_risk()          # HIGH_RISK (score: malicious + volume + email)
ds_required = breach.assess_art34_requirement()   # True

issues = breach.validate()
# → ["Art.33 SA notification urgent: 23.5h remaining"]

# After SA notification filed:
breach.sa_notification_status = NotificationStatus.SUBMITTED
breach.sa_notified_at = datetime(2026, 4, 16, 14, 30)
breach.sa_reference = "BfDI-2026-04-16-001"

Processor Breach Response Contracts

Processors must contractually guarantee breach notification timelines. The GDPR minimum ("without undue delay") is insufficient for controllers who need to meet the 72h window. Standard contract terms should include:

Controller-Processor DPA Breach Notification Clause (Art.28 / Art.33(2)):

The Processor shall:
(a) notify the Controller of any personal data breach within [24 hours] 
    of becoming aware of the breach;
(b) provide the Controller with the information specified in Art.33(3) 
    to the extent available at time of notification;
(c) not make any public communication about the breach without prior 
    Controller approval, except where required by applicable law;
(d) cooperate fully with the Controller's breach investigation and 
    provide supplementary information within [4 hours] of request;
(e) not notify any supervisory authority directly unless instructed by 
    the Controller or required by applicable law.

Engineering note: Cloud hosting providers operating as processors (including AWS, Azure, GCP, and EU-native PaaS providers) should have contractual breach notification SLAs. Verify your DPA addendum specifies a concrete timeline, not merely "without undue delay."


EDPB Guidelines 9/2022: Key Positions

EDPB Guidelines 9/2022 on personal data breach notification replace the earlier WP250 (Article 29 Working Party). Key positions relevant to engineers:

§7 — "Becomes aware": Confirmation is not required before notification; reasonable certainty suffices. Organisations cannot claim the 72h clock had not started because investigation was still ongoing.

§34 — Processors: Even where the DPA is silent on timeline, processors must notify "without undue delay" — EDPB recommends this means within 24-36 hours in practice.

§64 — Encryption exception (Art.34(3)(a)): The encryption exception applies only where (a) appropriate encryption was implemented, (b) the encryption key was not compromised, and (c) the encryption scheme is current (not deprecated). SHA-1 hashed passwords do not qualify.

§87 — High risk factors: Volume of data subjects affected, nature of personal data, ease of identification, and vulnerability of data subjects are all relevant. A combination of moderate factors can cumulatively reach the high-risk threshold.


Enforcement Cases

BfDI — German Federal Commissioner — 2023-11 — €1.2M

Incident: Online retailer — database backup transferred to unsecured S3 bucket. Discovered during routine audit. ~180,000 customer records (name, email, order history, partial card data).
Violation: SA not notified within 72h (notified on day 9). Data subjects not notified despite high-risk assessment (partial card data).
Fine: €1.2M. BfDI noted that the organisation's own incident response policy identified the breach as "critical" on day 1, eliminating any argument about the 72h clock not having started.

CNIL (France) — 2022-09 — €1.5M

Incident: Healthcare SaaS provider — breach notification filed on day 5. Approximately 490,000 patient health records accessed by external attacker.
Violation: Art.33 72h violation. Art.34 data subject notification never sent (argued Art.34(3)(b) remediation exception applied — CNIL rejected this because remediation was incomplete at the time of the decision).
Fine: €1.5M. CNIL emphasised that the Art.34(3) exceptions are fact-specific and narrow; the burden of demonstrating an exception is on the controller.

ICO (UK) — 2022-10 — £4.4M

Incident: NHS supplier — ransomware attack. Personal data of approximately 82,000 individuals. SA notification filed within 72 hours (compliant). But: notification was incomplete (no indication of scope, no risk assessment), and supplementary notifications were not filed for 31 days.
Violation: Art.33(4) phased notification obligation — not technically a non-notification, but the initial notification was inadequate. ICO treated incomplete initial notification as a violation.
Fine: £4.4M. Lesson: a compliant notification must contain the Art.33(3) mandatory fields to the extent possible at time of filing.

AP (Netherlands) — 2024-03 — €290K

Incident: Logistics company — accidental email disclosure. 100 customers received an email containing another customer's shipment data. No SA notification filed (controller assessed as "no risk").
Violation: AP disagreed with the risk assessment: shipment data included recipient home addresses, enabling physical security risks. AP found this was at minimum "risk" requiring Art.33 notification.
Fine: €290K. Key lesson: the "no risk" exception under Art.33(1) is narrow and the controller's burden to document and justify it is high. When in doubt, notify.


EU Hosting and Breach Surface Reduction

The geographic location of your data infrastructure directly affects breach risk vectors:

Third-country transfer elimination: Data stored exclusively in EU infrastructure does not flow across jurisdictions, eliminating the risk of government-ordered access (US CLOUD Act, FISA 702) that constitutes a confidentiality breach for EU data. EU-native cloud providers — including sota.io — provide infrastructure where this risk class does not apply.

SCCs as breach surface: When data crosses borders under SCCs, a foreign-government access order is not automatically notifiable as an Art.4(12) breach, but it creates compliance uncertainty. EU-only infrastructure removes this uncertainty entirely.

Incident response jurisdiction: A breach of data hosted in the EU is investigated by the EU SA under GDPR — consistent, predictable rules. Data hosted in multiple jurisdictions can trigger multi-regulator notifications (GDPR + local data protection laws), multiplying the administrative burden of an already complex incident.

Practical implication: For organisations aiming to minimise breach response complexity, EU-sovereign hosting removes an entire category of breach risk (foreign government access) and eliminates multi-jurisdiction notification scenarios.


30-Item Breach Response Checklist

Detection & Assessment

SA Notification (Art.33)

Data Subject Notification (Art.34)

Breach Register (Art.33(5))

Remediation

Post-Incident


Summary

GDPR Art.33 and Art.34 impose distinct, asymmetric obligations triggered by a personal data breach:

The Python implementation above provides a complete breach record structure with automatic deadline tracking, risk assessment, Art.34 obligation determination, and register export. Build these structures before the incident, not during it.


See Also