2026-04-21·15 min read·

DORA Art.19: Major ICT Incident Reporting — Three-Phase Timeline and NCA Notification for Financial Services (2026)

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

Art.19 is the article that turns DORA's incident management framework into a regulatory obligation. Art.17 defines how you must manage incidents internally. Art.18 defines when an incident is "major" enough to trigger external reporting. Art.19 is what you actually do once that threshold is crossed: notify your competent authority, follow a structured three-phase timeline, and submit reports using harmonised ITS templates.

Missing any Art.19 deadline is one of the most common findings in NCA supervisory examinations. The 4-hour initial notification deadline, in particular, is strict: it runs from the moment the entity classifies the incident as major, not from when the incident is discovered. Engineering teams that conflate detection time with classification time routinely miss the initial notification window.

This guide covers the complete Art.19 workflow, including the triggering criteria, what each phase requires, how the ITS templates work in practice, and a Python implementation that tracks incident state across all three phases.


1. Where Art.19 Sits in the DORA Chapter III Chain

ArticleFunctionDirectionHard Deadline
Art.17ICT incident management processInternalContinuous
Art.18Classification: major vs. non-majorInternalUpon detection
Art.19Mandatory reporting to NCAEntity → CA4h / 72h / 30d
Art.20Harmonised ITS formats and templatesStandardDefines Art.19 content
Art.21Centralised reporting hub routingInfrastructureRoutes Art.19 reports
Art.22Supervisory feedback on final reportsCA → EntityAfter Art.19 final
Art.23Voluntary threat notificationEntity → CAVoluntary

Art.19 is the only article in Chapter III with statutory hard deadlines. Every other article either precedes it (setting up the process and classification) or follows it (defining how reports are processed and how NCAs respond).


2. The Art.18 → Art.19 Trigger

Art.19 obligations activate the moment your Art.18 classification process concludes that an incident is "major." This classification is performed under the criteria established by the Commission Implementing Regulation (ITS) 2024/2956, which defines:

The critical operational implication: Art.19's 4-hour initial notification clock starts from classification, not from detection or incident onset. Your Art.17 incident management process must therefore include a classification checkpoint — a defined step where the incident response team formally determines whether the Art.18 materiality criteria are met. The timestamp of that determination is what the NCA will examine if the initial notification is late.

[Incident Detected]
       ↓
[Art.17 Incident Response Initiated — T₀]
       ↓
[Art.18 Classification Assessment — T₁]
   ├── Non-major → Internal tracking only
   └── Major → Art.19 trigger at T₁
                ↓
        Initial notification due: T₁ + 4h
        Intermediate report due: T₁ + 72h
        Final report due: T₁ + 30 days

The gap between T₀ (detection) and T₁ (classification) must be minimised but cannot be zero: classification requires a substantive assessment, not an instantaneous decision. NCA guidance accepts a classification delay of 1-2 hours for complex incidents where impact scope is still being determined. Beyond 3 hours without classification, NCAs expect documented justification in the intermediate report.


3. Phase 1 — Initial Notification (≤4 Hours)

3.1 What Art.19(4)(a) Requires

The initial notification must be submitted to the competent authority within 4 hours of major incident classification (or by 11:59 on the next business day if the classification occurs outside business hours under Art.19(4)(a) grace provisions).

The initial notification is intentionally limited in scope. Using the ITS 2024/2956 template, it must contain:

FieldRequired Detail
Entity identificationLEI code, entity name, NCA contact
Incident classificationClassification date/time, classification basis (which Art.18 criteria met)
Initial impact assessmentServices affected, estimated client impact (count or percentage), geographic scope
Operational statusWhether services are currently degraded, unavailable, or restored
Cause hypothesisInitial assessment only — "suspected ransomware," "third-party TSP outage," etc.
Containment statusActive, in-progress, or contained as of notification time

What the initial notification does NOT need:

NCAs have confirmed in supervisory guidance that an initial notification submitted within 4 hours with incomplete impact figures is acceptable — submitting a complete report after 6 hours is not.

3.2 The 4-Hour Deadline in Practice

For incidents that occur outside business hours, Art.19(4)(a) provides that the 4-hour window applies during business hours. If a major incident is classified at 23:00 on a Friday, the initial notification deadline is 11:59 on the following Monday (the next business day). However, most NCAs expect notification of severe incidents regardless of time, and supervisory practice has evolved toward expecting out-of-hours notification for high-impact incidents affecting critical financial market infrastructure.

Practical recommendation: build your Art.19 notification workflow to treat every major incident as if it requires immediate notification. The out-of-hours grace period exists for edge cases, not as standard operating procedure.


4. Phase 2 — Intermediate Report (≤72 Hours)

4.1 What Art.19(4)(b) Requires

The intermediate report must be submitted within 72 hours of major incident classification. It is a substantive update on incident progress and must include updated information on all initial notification fields plus:

Additional FieldDetail Level
Updated impact assessmentClient count confirmed, transaction volume affected, geographic scope confirmed
Root cause analysis (preliminary)Sufficient to establish whether attack vector or system failure, initial hypothesis confirmed or revised
Containment measures appliedSpecific technical measures implemented, with timestamps
Recovery progressSystems restored vs. remaining in degraded state, with percentage or status indicators
Third-party involvementWhether an ICT TSP was involved (triggers Art.28-30 implications); whether peer financial entities affected
Notification of public authoritiesWhether law enforcement, CSIRT, or data protection authority has been notified
Revised impact estimateUpdated client impact numbers; any data breach confirmation

4.2 If the Incident Is Resolved Before 72 Hours

Art.19(4) allows financial entities to submit a combined intermediate/final report if the incident is fully resolved before the 72-hour intermediate report deadline. In this case, a single report covering all fields from both phases is acceptable. NCAs expect this to happen only for contained, low-complexity incidents. If your 72-hour timeline shows ongoing remediation but you try to file a combined report, NCAs will flag it.


5. Phase 3 — Final Report (≤1 Month)

5.1 What Art.19(4)(c) Requires

The final report must be submitted within one calendar month from the date the financial entity sends the intermediate report. This is not one month from classification — it is one month from the intermediate submission. Entities that submit their intermediate report early therefore get a longer window for the final report (though the outer bound remains approximately 33 days from classification in the default case).

The final report must contain:

SectionRequired Content
Complete impact assessmentFinal confirmed client count, transaction volume, service unavailability duration, geographic scope
Root cause analysis (final)Definitive root cause determination with supporting evidence; specific vulnerability, system, or process failure identified
Cross-entity impactWhether other financial entities were affected (for TSP-originated incidents or attack campaigns)
Remediation measuresAll corrective actions taken, with implementation dates; outstanding items with committed timeline
Lessons learnedProcess improvements implemented or planned as result of incident
Recurrence preventionSpecific technical or governance controls added to prevent recurrence
IT staff involvedBrief summary of internal and external (vendor, forensics firm) resources engaged
Interaction with other frameworksWhether parallel GDPR Art.33/34, NIS2 Art.23, or other reporting obligations were triggered and how they were coordinated

5.2 The Final Report as a Compliance Record

The final report is the primary document an NCA will examine during supervisory reviews and on-site inspections. NCAs look for three things specifically:

  1. Root cause depth: Was the actual system failure or process gap identified, or was the report filed with a generic "external cyberattack" description?
  2. Remediation specificity: Are corrective actions concrete (e.g., "patched CVE-2024-XXXX on 15 production servers by 2026-04-30") or vague ("improving security posture")?
  3. Framework integration: For incidents that also triggered GDPR or NIS2 reporting, was the Art.19 final report consistent with those filings?

6. Cross-Border Reporting — Multi-Jurisdiction Entities

Financial entities operating in more than one EU member state face additional Art.19 complexity. The general rule is report to the competent authority of the home member state (the NCA where the entity is authorised). However:

ScenarioReporting Requirement
Branch operations in other member statesHome NCA reports; host NCAs notified by home NCA under Art.49 cooperation protocols
Subsidiary with separate authorisation in another member stateBoth entities have independent Art.19 obligations to their respective NCAs
Incident affects clients exclusively in another member stateStill report to home NCA; home NCA shares with host NCA under Art.49
TSP incident affecting entities across multiple member statesEach affected entity reports to its own NCA; ESAs coordinate under Art.21 centralised hub
DORA entity also subject to NIS2 as essential entityParallel reporting: Art.19 to NCA + NIS2 Art.23 to CSIRT. Art.19 report does NOT satisfy NIS2 obligation or vice versa

For most entities, the practical rule is simple: one NCA, one reporting chain. Complexity arises for cross-border banking groups and for incidents where an ICT TSP failure cascades across multiple subsidiaries with separate authorisations.


7. Voluntary Notification Under Art.19(2)

Art.19(2) allows financial entities to voluntarily notify their competent authority of cyber threats that do not yet meet the Art.18 "major incident" materiality threshold but which are nonetheless significant. This voluntary notification option (expanded in Art.23) allows entities to:

Voluntary notifications under Art.19(2) are not subject to the 4-hour/72-hour/30-day timeline. They use a lighter template format defined in ITS 2024/2956 Annex II. NCAs cannot penalise entities for not filing voluntary notifications, but supervisory practice has established that entities which proactively notify tend to receive more favourable feedback during formal examinations.


8. Interaction with Parallel Reporting Obligations

Art.19 does not exist in isolation. Major ICT incidents frequently trigger parallel reporting obligations:

FrameworkTriggerDeadlineCoordination with Art.19
GDPR Art.33Personal data breach72 hours to DPACoordinate timing; Art.19 intermediate and GDPR initial often overlap at 72h window
NIS2 Art.23Significant incident for essential/important entities24h initial / 72h intermediate / 1 month finalSimilar three-phase structure; different NCA vs. CSIRT recipient; not substitutable
MiCA Art.69Crypto-asset service provider incidentAlign with DORA for CASPsDORA lex specialis for CASPs that are also MiCA-authorised
PSD2 / DSP2Operational or security incidentEBA reporting via NCANCA may route combined DORA + PSD2 reporting through single contact point

The most common error is assuming that submitting an Art.19 report satisfies the NIS2 Art.23 obligation. It does not. NIS2 Art.23 requires notification to the CSIRT (or NIS2 competent authority, which may differ from the DORA NCA), uses different templates, and covers a broader category of incidents.


9. Python Implementation: ICT Incident Reporter with Phase State Machine

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

logger = logging.getLogger(__name__)


class IncidentPhase(str, Enum):
    DETECTED = "detected"
    CLASSIFIED_MAJOR = "classified_major"
    INITIAL_SENT = "initial_sent"
    INTERMEDIATE_SENT = "intermediate_sent"
    FINAL_SENT = "final_sent"
    RESOLVED = "resolved"


class BusinessHoursHelper:
    """Calculates business-day-aware deadlines per Art.19(4) grace provisions."""

    BUSINESS_START = 9   # 09:00
    BUSINESS_END = 18    # 18:00

    @classmethod
    def next_business_deadline(cls, from_dt: datetime, hours: int) -> datetime:
        """Calculate deadline accounting for out-of-hours grace period."""
        if cls._is_business_hours(from_dt):
            return from_dt + timedelta(hours=hours)
        # Classification outside business hours: deadline is next business day 11:59
        next_day = from_dt.replace(hour=11, minute=59, second=0, microsecond=0)
        if from_dt.hour >= cls.BUSINESS_END:
            next_day += timedelta(days=1)
        # Skip weekends
        while next_day.weekday() >= 5:
            next_day += timedelta(days=1)
        return next_day

    @classmethod
    def _is_business_hours(cls, dt: datetime) -> bool:
        return dt.weekday() < 5 and cls.BUSINESS_START <= dt.hour < cls.BUSINESS_END


@dataclass
class MajorIncident:
    incident_id: str
    entity_lei: str
    entity_name: str
    nca: str

    # Timestamps
    detection_time: datetime
    classification_time: Optional[datetime] = None
    initial_sent_time: Optional[datetime] = None
    intermediate_sent_time: Optional[datetime] = None
    final_sent_time: Optional[datetime] = None

    # Phase data
    phase: IncidentPhase = IncidentPhase.DETECTED
    initial_payload: dict = field(default_factory=dict)
    intermediate_payload: dict = field(default_factory=dict)
    final_payload: dict = field(default_factory=dict)

    # Computed deadlines (set on classification)
    initial_deadline: Optional[datetime] = None
    intermediate_deadline: Optional[datetime] = None
    final_deadline: Optional[datetime] = None

    def classify_as_major(self, classification_time: datetime, art18_criteria: list[str]) -> None:
        """Art.18 → Art.19 trigger. Sets all three phase deadlines."""
        if self.phase != IncidentPhase.DETECTED:
            raise ValueError(f"Cannot classify from phase {self.phase}")

        self.classification_time = classification_time
        self.phase = IncidentPhase.CLASSIFIED_MAJOR

        helper = BusinessHoursHelper()
        # Art.19(4)(a): initial within 4 hours
        self.initial_deadline = helper.next_business_deadline(classification_time, hours=4)
        # Art.19(4)(b): intermediate within 72 hours
        self.intermediate_deadline = classification_time + timedelta(hours=72)
        # Art.19(4)(c): final within 1 month of intermediate submission
        # Set tentatively; will be recalculated when intermediate is sent
        self.final_deadline = classification_time + timedelta(days=33)

        logger.info(
            "Incident %s classified as major at %s. "
            "Deadlines: initial=%s, intermediate=%s, final=%s",
            self.incident_id,
            classification_time.isoformat(),
            self.initial_deadline.isoformat(),
            self.intermediate_deadline.isoformat(),
            self.final_deadline.isoformat(),
        )

        self.initial_payload["classification_criteria"] = art18_criteria

    def submit_initial(self, payload: dict) -> dict:
        """Submit initial Art.19(4)(a) notification."""
        now = datetime.utcnow()
        if self.phase != IncidentPhase.CLASSIFIED_MAJOR:
            raise ValueError(f"Cannot submit initial from phase {self.phase}")
        if now > self.initial_deadline:
            logger.warning(
                "LATE SUBMISSION: Initial notification for %s submitted at %s, deadline was %s",
                self.incident_id, now.isoformat(), self.initial_deadline.isoformat()
            )

        required = ["services_affected", "client_impact_estimate", "operational_status",
                    "cause_hypothesis", "containment_status"]
        missing = [f for f in required if f not in payload]
        if missing:
            raise ValueError(f"Initial notification missing required fields: {missing}")

        self.initial_payload.update(payload)
        self.initial_payload["submission_time"] = now.isoformat()
        self.initial_sent_time = now
        self.phase = IncidentPhase.INITIAL_SENT
        return self._build_its_envelope("initial")

    def submit_intermediate(self, payload: dict, combined_final: bool = False) -> dict:
        """Submit intermediate Art.19(4)(b) report."""
        now = datetime.utcnow()
        if self.phase != IncidentPhase.INITIAL_SENT:
            raise ValueError(f"Cannot submit intermediate from phase {self.phase}")

        required = ["root_cause_preliminary", "containment_measures", "recovery_progress",
                    "third_party_involvement", "updated_client_impact"]
        missing = [f for f in required if f not in payload]
        if missing:
            raise ValueError(f"Intermediate report missing required fields: {missing}")

        self.intermediate_payload.update(payload)
        self.intermediate_payload["submission_time"] = now.isoformat()
        self.intermediate_sent_time = now

        # Recalculate final deadline from actual intermediate submission time
        self.final_deadline = now + timedelta(days=30)

        if combined_final:
            self.phase = IncidentPhase.FINAL_SENT
            self.final_sent_time = now
            logger.info("Combined intermediate/final report for %s (incident resolved)", self.incident_id)
        else:
            self.phase = IncidentPhase.INTERMEDIATE_SENT

        return self._build_its_envelope("intermediate", combined_final=combined_final)

    def submit_final(self, payload: dict) -> dict:
        """Submit final Art.19(4)(c) report."""
        now = datetime.utcnow()
        if self.phase != IncidentPhase.INTERMEDIATE_SENT:
            raise ValueError(f"Cannot submit final from phase {self.phase}")
        if now > self.final_deadline:
            logger.warning(
                "LATE SUBMISSION: Final report for %s submitted at %s, deadline was %s",
                self.incident_id, now.isoformat(), self.final_deadline.isoformat()
            )

        required = ["root_cause_final", "remediation_measures", "lessons_learned",
                    "recurrence_prevention", "confirmed_client_impact"]
        missing = [f for f in required if f not in payload]
        if missing:
            raise ValueError(f"Final report missing required fields: {missing}")

        self.final_payload.update(payload)
        self.final_payload["submission_time"] = now.isoformat()
        self.final_sent_time = now
        self.phase = IncidentPhase.FINAL_SENT
        return self._build_its_envelope("final")

    def _build_its_envelope(self, phase: str, combined_final: bool = False) -> dict:
        """Build ITS 2024/2956 compliant submission envelope."""
        return {
            "its_template_version": "ITS-2024-2956-v1",
            "report_type": "combined_intermediate_final" if combined_final else phase,
            "incident_id": self.incident_id,
            "entity": {
                "lei": self.entity_lei,
                "name": self.entity_name,
                "nca": self.nca,
            },
            "timestamps": {
                "detection": self.detection_time.isoformat(),
                "classification": self.classification_time.isoformat() if self.classification_time else None,
                "initial_submission": self.initial_sent_time.isoformat() if self.initial_sent_time else None,
                "intermediate_submission": self.intermediate_sent_time.isoformat() if self.intermediate_sent_time else None,
                "final_submission": self.final_sent_time.isoformat() if self.final_sent_time else None,
            },
            "deadlines": {
                "initial": self.initial_deadline.isoformat() if self.initial_deadline else None,
                "intermediate": self.intermediate_deadline.isoformat() if self.intermediate_deadline else None,
                "final": self.final_deadline.isoformat() if self.final_deadline else None,
            },
            "payload": getattr(self, f"{phase}_payload"),
        }

    def deadline_status(self) -> dict:
        """Return current deadline status for monitoring dashboards."""
        now = datetime.utcnow()
        return {
            "incident_id": self.incident_id,
            "phase": self.phase.value,
            "initial": {
                "deadline": self.initial_deadline.isoformat() if self.initial_deadline else None,
                "submitted": self.initial_sent_time.isoformat() if self.initial_sent_time else None,
                "status": "submitted" if self.initial_sent_time else (
                    "overdue" if self.initial_deadline and now > self.initial_deadline else "pending"
                ),
            },
            "intermediate": {
                "deadline": self.intermediate_deadline.isoformat() if self.intermediate_deadline else None,
                "submitted": self.intermediate_sent_time.isoformat() if self.intermediate_sent_time else None,
                "status": "submitted" if self.intermediate_sent_time else (
                    "overdue" if self.intermediate_deadline and now > self.intermediate_deadline else "pending"
                ),
            },
            "final": {
                "deadline": self.final_deadline.isoformat() if self.final_deadline else None,
                "submitted": self.final_sent_time.isoformat() if self.final_sent_time else None,
                "status": "submitted" if self.final_sent_time else (
                    "overdue" if self.final_deadline and now > self.final_deadline else "pending"
                ),
            },
        }

10. Common Art.19 Compliance Failures

Based on NCA supervisory findings published by EBA/EIOPA/ESMA, these are the most frequent Art.19 violations:

Failure TypeRoot CauseNCA Finding
Late initial notificationClassification delayed >3h; 4h clock not started from classificationMinor finding (first offence); formal measure (repeat)
Detection vs. classification confusionIncident management process does not have explicit classification checkpointSystemic process finding; requires written remediation plan
Incomplete intermediate reportRoot cause section filed with hypothesis unchanged from initialFeedback letter requesting supplemental root cause detail
No final report filedEntity closed incident internally without completing Art.19 cycleMost serious finding; can trigger Art.46-55 supervisory measures
NIS2 + DORA conflationArt.19 final report cited as fulfilling NIS2 Art.23 obligationSeparate finding for both frameworks; dual remediation required
Third-party incident not reportedTSP outage caused client impact above Art.18 threshold; entity classified as non-majorRetroactive classification review; Art.28 documentation request

The most consequential failure on this list is the last: incidents caused by ICT TSP failures (cloud provider outages, SaaS platform incidents) are the most commonly missed Art.19 triggers. Financial entities often apply Art.18 classification criteria narrowly to internally-originated incidents and fail to classify externally-caused incidents that nonetheless cause client impact above the Art.18 threshold.


11. Art.19 Compliance Checklist (20 Items)

Classification and Trigger:

Initial Notification (4 hours):

Intermediate Report (72 hours):

Final Report (≤1 month from intermediate):


See Also