GovStack: het modulaire raamwerk voor de digitale overheid

GovStack is een internationaal initiatief dat in 2020 werd gelanceerd door Estland, Duitsland, ITU (Internationale Telecommunicatie-unie) e DIAL (Digitale Impact Alliantie) met het doel van het delen van de tools, kennis en best practices die nodig zijn om diensten te bouwen digitaal publiek op grote schaal, zonder telkens opnieuw te hoeven beginnen.

Het fundamentele idee is eenvoudig maar krachtig: in plaats van het ontwikkelen van monolithische systemen per land, elk De digitale dienst van de overheid wordt opgesplitst in BouwBlok (functionele modules) die ze bieden een specifieke mogelijkheid (identiteit, betalingen, berichtenuitwisseling, registers, enz.) en dat ze kunnen vrij worden gecombineerd om elke dienst te bouwen. Bouwstenen zijn dat wel interoperabel, herbruikbaar en implementatie-onafhankelijk: GovStack definieert de specificaties, niet de software.

In 2025, met de lancering van Overheidsspecificaties 2.0 (de strategie 2025-2027), heeft GovStack de raamwerk door nieuwe bouwstenen te integreren, interoperabiliteitsspecificaties bij te werken en te definiëren een gelaagd volwassenheidsmodel om landen met veel digitale capaciteit te ondersteunen anders. Meer dan 20 landen maken actief gebruik van de GovStack-aanpak.

Wat je gaat leren

  • De 9 fundamentele bouwstenen van GovStack en hun technische specificaties
  • Hoe bestaande Italiaanse PA-services (SPID, CIE, pagoPA) in kaart te brengen op GovStack Building Blocks
  • GovStack-referentiearchitectuur: gelaagd model en integratiebus
  • GovSpecs 2.0: wat is er nieuw in de strategie 2025-2027
  • Praktische implementatie van een Building Block Identity met OpenID Connect
  • Building Block Payments: integratie met nationale betalingssystemen
  • Toestemmingsbouwsteen: AVG-conform toestemmingsbeheer
  • Hoe u kunt evalueren of GovStack geschikt is voor uw context

De 9 fundamentele bouwstenen

GovStack definieert 9 kernbouwstenen (gepubliceerd in de nieuwe 2025-specificatie) die de cross-functionaliteiten die nodig zijn voor elke digitale overheidsdienst:

BouwBlok Functie Protocollen / Standaarden Italiaans voorbeeld
Identiteit Authenticatie en identiteitsbeheer OIDC, SAML, W3C DEED SPID, CIE, EUDI-portemonnee
Betalingen Het verwerken van betalingen en overboekingen ISO 20022, REST-API betaalPA
Toestemming Verzamelen en beheren van toestemmingen AVG-, DPIA- en OAuth-bereiken CMP met AVG-by-Design
Digitale registers Beheer van gezaghebbende registers (registratiekantoor, kadaster) REST-API, FHIR, CKAN ANPR, kadaster, beroepsregister
Berichten Veilige communicatie tussen overheid en burgers SMTP, Push, WebSocket, MQTT IO App, PEC, pagoPA-meldingen
Informatiebemiddeling Veilige gegevensuitwisseling tussen systemen X-Road, REST, GraphQL PDND, AgID-interoperabiliteit
Registratie Registratie van personen/entiteiten voor diensten REST-API, OAuth 2.0 INPS-portaal, SUAP, gemeentelijke portalen
Planner Beheer van afspraken en reserveringen iCalendar, REST-API Gezondheid CUP, digitaal loket
Werkstroom Orkestratie van processen en procedures BPMN, SAGA-patroon, REST PA praktijkmanagementsystemen

GovStack-referentiearchitectuur

GovStack stelt een gelaagde architectuur voor waarin de bouwstenen in lagen worden geplaatst onderscheiden met duidelijke verantwoordelijkheden:

  • Laag 0 - Infrastructuur: cloud, netwerk, beveiliging. BB’s zijn niet afhankelijk van een specifieke cloud; ze kunnen draaien op AWS, Azure, GCP of on-premise infrastructuur.
  • Laag 1 - Kernbouwstenen: de 9 fundamentele BB’s. Het zijn autonome diensten die blootleggen Gestandaardiseerde RESTful API's. Elke BB heeft een openbare specificatie (OpenAPI + JSON-schema's).
  • Laag 2 - Gedeelde services: transversale diensten zoals gecentraliseerde logging, service mesh, API-gateway, servicedetectie. Deze services ondersteunen alle BB's.
  • Laag 3 - Toepassingen: daadwerkelijke digitale dienstverlening (schoolinschrijving, certificaataanvraag, betaling van zegelrecht) die de onderliggende BB's orkestreren.
# Architettura di un servizio PA con Building Block GovStack
# Esempio: Servizio di iscrizione scolastica online

# Il servizio orchestra 5 Building Block:
# 1. Identity BB - autenticazione genitore con SPID/CIE
# 2. Registration BB - raccolta dati del bambino
# 3. Digital Registries BB - verifica iscrizione anagrafica
# 4. Consent BB - consenso al trattamento dati minori
# 5. Messaging BB - conferma iscrizione via IO App/email
# 6. Payments BB - pagamento tassa iscrizione via pagoPA

from dataclasses import dataclass
from typing import Optional
import httpx

@dataclass
class SchoolEnrollmentRequest:
    parent_session_token: str   # Token da Identity BB (SPID/CIE)
    child_fiscal_code: str
    school_code: str
    year: int

class SchoolEnrollmentService:
    """
    Servizio iscrizione scolastica che orchestra i Building Block GovStack.
    Pattern: Saga orchestrator (gestione stati distribuiti con compensazioni).
    """

    def __init__(
        self,
        identity_bb_url: str,
        registry_bb_url: str,
        consent_bb_url: str,
        messaging_bb_url: str,
        payments_bb_url: str
    ):
        self.identity_url = identity_bb_url
        self.registry_url = registry_bb_url
        self.consent_url = consent_bb_url
        self.messaging_url = messaging_bb_url
        self.payments_url = payments_bb_url

    async def process_enrollment(self, request: SchoolEnrollmentRequest) -> dict:
        """
        Saga: processo iscrizione con compensazioni in caso di errore.
        Ogni step è idempotente e reversibile.
        """
        saga_log = []

        try:
            # Step 1: Verifica identità genitore tramite Identity BB
            parent_identity = await self._verify_parent_identity(
                request.parent_session_token
            )
            saga_log.append({"step": "identity_verified", "status": "ok"})

            # Step 2: Verifica residenza bambino tramite Digital Registries BB
            child_registry = await self._verify_child_in_registry(
                request.child_fiscal_code,
                parent_identity["fiscal_number"]
            )
            saga_log.append({"step": "registry_verified", "status": "ok"})

            # Step 3: Raccolta consenso GDPR tramite Consent BB
            consent_id = await self._collect_consent(
                parent_identity["fiscal_number"],
                purpose="school_enrollment_data_processing"
            )
            saga_log.append({"step": "consent_collected", "consent_id": consent_id})

            # Step 4: Registrazione iscrizione tramite Registration BB
            enrollment_id = await self._register_enrollment(
                child_fc=request.child_fiscal_code,
                school_code=request.school_code,
                year=request.year,
                parent_fc=parent_identity["fiscal_number"]
            )
            saga_log.append({"step": "enrollment_registered", "enrollment_id": enrollment_id})

            # Step 5: Pagamento tassa (se prevista) tramite Payments BB
            payment_url = await self._create_payment(
                parent_fc=parent_identity["fiscal_number"],
                enrollment_id=enrollment_id,
                amount_cents=1500  # 15 euro
            )
            saga_log.append({"step": "payment_created", "payment_url": payment_url})

            # Step 6: Notifica conferma tramite Messaging BB
            await self._send_confirmation(
                parent_fc=parent_identity["fiscal_number"],
                enrollment_id=enrollment_id,
                payment_url=payment_url
            )
            saga_log.append({"step": "notification_sent", "status": "ok"})

            return {
                "status": "success",
                "enrollment_id": enrollment_id,
                "payment_url": payment_url,
                "saga_log": saga_log
            }

        except Exception as e:
            # Compensazione: rollback degli step completati in ordine inverso
            await self._compensate(saga_log, e)
            raise

    async def _verify_parent_identity(self, session_token: str) -> dict:
        """Chiama il Building Block Identity per validare il token SPID/CIE."""
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.identity_url}/v1/tokens/validate",
                json={"token": session_token}
            )
            response.raise_for_status()
            return response.json()  # Returns: fiscal_number, name, surname, etc.

    async def _verify_child_in_registry(self, child_fc: str, parent_fc: str) -> dict:
        """Chiama il Building Block Digital Registries per verificare l'anagrafe."""
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{self.registry_url}/v1/citizens/{child_fc}/family-relations",
                params={"parent_fiscal_code": parent_fc}
            )
            response.raise_for_status()
            return response.json()

    async def _collect_consent(self, parent_fc: str, purpose: str) -> str:
        """Registra il consenso tramite il Building Block Consent."""
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.consent_url}/v1/consents",
                json={
                    "citizen_pseudonym": self._pseudonymize(parent_fc),
                    "purpose": purpose,
                    "legal_basis": "GDPR Art. 6(1)(e)",
                    "version": "2025-01"
                }
            )
            response.raise_for_status()
            return response.json()["consent_id"]

    async def _compensate(self, saga_log: list, error: Exception):
        """
        Compensazione Saga: rollback in ordine inverso degli step completati.
        Garantisce consistenza anche in caso di errori parziali.
        """
        for step in reversed(saga_log):
            try:
                if step["step"] == "consent_collected":
                    await self._revoke_consent(step["consent_id"])
                elif step["step"] == "enrollment_registered":
                    await self._cancel_enrollment(step["enrollment_id"])
            except Exception as compensation_error:
                # Logga l'errore di compensazione ma continua
                print(f"Compensation error for {step['step']}: {compensation_error}")

    def _pseudonymize(self, fiscal_code: str) -> str:
        import hashlib, hmac
        key = b"secret-vault-key"  # In produzione: usa un HSM
        return hmac.new(key, fiscal_code.encode(), hashlib.sha256).hexdigest()[:32]

GovSpecs 2.0: wat is er nieuw in de strategie 2025-2027

Overheidsspecificaties 2.0, aangekondigd door GovStack in 2025, introduceert belangrijke updates ten opzichte van naar de vorige versie van de specificaties:

  • Volwassenheidsmodel op 4 niveaus: vanaf "Initiële fase" (alleen training en architectuur hoog niveau) tot "geavanceerde integratie" (meerdere BB's geïntegreerd in nationale interoperabiliteitskaders). Hierdoor kunnen landen GovStack adopteren volgens hun eigen prioriteiten en mogelijkheden.
  • Ondersteuning voor verifieerbare referenties: Building Block Identity bevat nu specificaties voor W3C verifieerbare inloggegevens, in lijn met eIDAS 2.0 en het Europese EUDI Wallet-programma.
  • NAAR BB: een nieuwe bouwsteen voor de integratie van AI-componenten (taalmodellen, voorspellen, classificeren) in overheidsdiensten, met aandacht voor transparantie en verklaarbaarheid.
  • OpenAPI 3.1-specificaties: Alle BB API's zijn nu gedocumenteerd met OpenAPI 3.1, met Voltooi JSON-schema's en testbare voorbeelden via SwaggerUI.
  • Conformiteitstesten: Een geautomatiseerd raamwerk voor het verifiëren van een implementatie van BB voldoet aan de GovStack-specificaties.

Implementatie van Building Block Identity met OIDC

De Building Block Identity is het meest kritisch en het meest complex om te implementeren. GovStack specificeert dat het de volgende API's moet vrijgeven:

# Building Block Identity - Implementazione minima conforme GovStack
# OpenAPI 3.1 compatible - FastAPI implementation

from fastapi import FastAPI, HTTPException, Header, Depends
from fastapi.security import HTTPBearer
from pydantic import BaseModel
from typing import Optional
import jwt

app = FastAPI(
    title="Identity Building Block",
    description="GovStack Identity BB - Conforme a GovSpecs 2.0",
    version="2.0.0"
)

security = HTTPBearer()

# --- Modelli ---

class TokenValidationRequest(BaseModel):
    token: str
    expected_acr: Optional[str] = None  # Livello autenticazione richiesto

class IdentityResponse(BaseModel):
    sub: str                    # Identificativo presso l'IdP
    fiscal_number: Optional[str] = None  # Codice Fiscale (SPID/CIE)
    given_name: str
    family_name: str
    birthdate: Optional[str] = None
    acr: str                    # Livello autenticazione effettivo
    auth_time: int              # Timestamp autenticazione
    session_valid_until: int    # Scadenza sessione

class SessionCreationRequest(BaseModel):
    idp_id: str                 # Identity Provider scelto dall'utente
    redirect_uri: str
    acr_values: str = "https://www.spid.gov.it/SpidL2"
    scope: list = ["openid", "profile"]
    ui_locales: str = "it"

# --- Endpoints del Building Block Identity ---

@app.post("/v1/sessions",
    summary="Avvia sessione di autenticazione",
    tags=["Sessions"],
    response_model=dict)
async def create_authentication_session(request: SessionCreationRequest) -> dict:
    """
    GovStack Identity BB - Avvia il flusso di autenticazione.
    Restituisce l'URL di redirect verso l'IdP.
    """
    # Genera state e nonce per sicurezza
    import secrets
    session_id = secrets.token_urlsafe(32)
    state = secrets.token_urlsafe(32)
    nonce = secrets.token_urlsafe(32)

    # Recupera metadata dell'IdP dalla federazione OIDC
    idp_metadata = await get_idp_metadata(request.idp_id)

    # Costruisce URL autorizzazione (PKCE + Request Object)
    auth_url = build_oidc_auth_url(
        idp_auth_endpoint=idp_metadata["authorization_endpoint"],
        client_id=CLIENT_ID,
        redirect_uri=request.redirect_uri,
        scope=request.scope,
        state=state,
        nonce=nonce,
        acr_values=request.acr_values,
        private_key=SIGNING_KEY
    )

    # Salva sessione (Redis o DB)
    await session_store.save(session_id, {
        "state": state, "nonce": nonce, "idp_id": request.idp_id
    })

    return {"session_id": session_id, "authorization_url": auth_url}

@app.post("/v1/tokens/validate",
    summary="Valida un token di sessione",
    tags=["Tokens"],
    response_model=IdentityResponse)
async def validate_token(request: TokenValidationRequest) -> IdentityResponse:
    """
    GovStack Identity BB - Valida un token e restituisce l'identità.
    I servizi chiamanti usano questo endpoint per verificare l'autenticazione.
    """
    try:
        # Decodifica e valida il token (firma, scadenza, audience)
        claims = jwt.decode(
            request.token,
            JWKS,
            algorithms=["RS256"],
            audience=CLIENT_ID
        )

        # Verifica livello autenticazione se richiesto
        if request.expected_acr:
            actual_acr = claims.get("acr", "")
            if not _meets_acr_requirement(actual_acr, request.expected_acr):
                raise HTTPException(
                    status_code=403,
                    detail=f"Insufficient authentication level. Required: {request.expected_acr}"
                )

        return IdentityResponse(
            sub=claims["sub"],
            fiscal_number=claims.get("fiscal_number"),
            given_name=claims["given_name"],
            family_name=claims["family_name"],
            birthdate=claims.get("birthdate"),
            acr=claims.get("acr", ""),
            auth_time=claims.get("auth_time", 0),
            session_valid_until=claims.get("exp", 0)
        )
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token expired")
    except jwt.InvalidTokenError as e:
        raise HTTPException(status_code=401, detail=f"Invalid token: {str(e)}")

@app.delete("/v1/sessions/{session_id}",
    summary="Termina sessione (logout)",
    tags=["Sessions"])
async def end_session(session_id: str) -> dict:
    """
    GovStack Identity BB - Logout con propagazione verso l'IdP.
    Implementa OIDC Back-Channel Logout per notificare tutti i RP attivi.
    """
    session = await session_store.get(session_id)
    if not session:
        raise HTTPException(status_code=404, detail="Session not found")

    # Notifica logout all'IdP (OIDC Back-Channel Logout)
    idp_metadata = await get_idp_metadata(session["idp_id"])
    if "end_session_endpoint" in idp_metadata:
        await propagate_logout(idp_metadata["end_session_endpoint"], session)

    await session_store.delete(session_id)
    return {"status": "session_terminated"}

def _meets_acr_requirement(actual: str, required: str) -> bool:
    """Verifica che il livello di autenticazione effettivo soddisfi il requisito."""
    ACR_LEVELS = {
        "https://www.spid.gov.it/SpidL1": 1,
        "https://www.spid.gov.it/SpidL2": 2,
        "https://www.spid.gov.it/SpidL3": 3,
        "https://www.cie.gov.it/cie/aa": 2,
    }
    return ACR_LEVELS.get(actual, 0) >= ACR_LEVELS.get(required, 0)

Building Block Messaging: IO-app en overheidsmeldingen

Building Block Messaging beheert de communicatie tussen overheid en burgers. In Italië is de dienst meer in lijn met de GovStack Messaging-specificatie is Ik App (io.italia.it), de nationale app voor communicatie met de PA beheerd door PagoPA S.p.A.

# Building Block Messaging - Integrazione con IO App
# API REST di IO App per invio messaggi ai cittadini

import httpx
from pydantic import BaseModel
from typing import Optional

class IOMessage(BaseModel):
    fiscal_code: str           # Codice Fiscale del destinatario
    time_to_live: int = 3600  # Secondi di validità notifica push
    content: dict              # Contenuto del messaggio
    default_addresses: Optional[dict] = None  # Fallback email

class IOAppClient:
    """
    Client per le API di IO App.
    Le API IO App sono disponibili su https://developer.io.italia.it
    """

    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.io.italia.it/api/v1"

    async def send_message(self, message: IOMessage) -> dict:
        """
        Invia un messaggio a un cittadino tramite IO App.
        Il cittadino deve aver attivato il proprio profilo IO.
        """
        async with httpx.AsyncClient() as client:
            response = await client.post(
                f"{self.base_url}/messages",
                headers={
                    "Ocp-Apim-Subscription-Key": self.api_key,
                    "Content-Type": "application/json"
                },
                json={
                    "fiscal_code": message.fiscal_code,
                    "time_to_live": message.time_to_live,
                    "content": message.content,
                    "default_addresses": message.default_addresses
                }
            )
            response.raise_for_status()
            return response.json()

    async def check_profile(self, fiscal_code: str) -> bool:
        """
        Verifica se un cittadino ha attivato il profilo IO e accetta messaggi dalla PA.
        """
        async with httpx.AsyncClient() as client:
            response = await client.get(
                f"{self.base_url}/profiles/{fiscal_code}",
                headers={"Ocp-Apim-Subscription-Key": self.api_key}
            )
            if response.status_code == 404:
                return False
            response.raise_for_status()
            profile = response.json()
            return profile.get("sender_allowed", False)

# Utilizzo: notifica iscrizione scolastica
async def notify_enrollment(fiscal_code: str, enrollment_id: str, payment_url: str):
    io_client = IOAppClient(api_key="your-io-api-key")

    # Verifica se il cittadino usa IO App
    has_io = await io_client.check_profile(fiscal_code)

    if has_io:
        # Messaggio strutturato IO App con CTA pagamento
        message = IOMessage(
            fiscal_code=fiscal_code,
            content={
                "subject": f"Iscrizione Scolastica {enrollment_id} - Conferma",
                "markdown": f"""
## La tua iscrizione è stata registrata

L'iscrizione con codice **{enrollment_id}** è stata registrata con successo.

Per completare la procedura, effettua il pagamento della tassa di iscrizione tramite il
link qui sotto.

**Importo**: 15,00 €

[Paga ora]({payment_url})

Per assistenza: [Ufficio Scolastico](https://istruzione.comune.esempio.it)
""",
                "payment_data": {
                    "amount": 1500,
                    "notice_number": enrollment_id,
                    "payee": {
                        "fiscal_code": "COMUNE_FC",
                        "name": "Comune di Esempio"
                    }
                }
            },
            default_addresses={"email": None}  # No fallback email
        )
        await io_client.send_message(message)
    else:
        # Fallback: email tradizionale (da implementare)
        await send_email_notification(fiscal_code, enrollment_id, payment_url)

GovStack-kaart van het Italiaanse ecosysteem

Italië beschikt al over een geavanceerd PPE-ecosysteem dat op natuurlijke wijze aansluit bij de GovStack Building Blocks. Voor een Italiaanse ontwikkelaar betekent het adopteren van GovStack niet dat je helemaal opnieuw moet beginnen: het betekent bestaande integraties reorganiseren volgens een gestandaardiseerd modulair model.

BB GovStack Italiaanse implementatie Volwassenheidsniveau Te vullen gat
Identiteit SPID, CIE, eIDAS Hoog (niveau 3-4) Voltooi OIDC-unificatie, EUDI Wallet
Betalingen betaalPA Hoog (niveau 4) Volledige GPD REST API
Berichten IO-app, PEC Hoog (niveau 3) Penetratie van IO-apps (in sommige gebieden nog steeds laag)
Digitale registers ANPR, kadaster, PDND Gemiddeld (niveau 2-3) Interoperabiliteit tussen registers is nog steeds gedeeltelijk
Informatiebemiddeling PDND Gemiddeld (niveau 2) De adoptie van PDND groeit nog steeds
Toestemming Diverse (niet gestandaardiseerd) Laag (niveau 1) We hebben een gecentraliseerd consensusplatform nodig
Planner Regionale gezondheidszorg CUP Laag-Midden (niveau 1-2) Regionale fragmentatie; geen nationale standaard
Werkstroom PA oefensystemen (heterogeen) Laag (niveau 1) Grote heterogeniteit; standaardisatie is nodig

Wanneer moet u GovStack adopteren?

GovStack is met name geschikt wanneer:

  • Je bouwt een nieuwe PA digitale dienst helemaal opnieuw beginnen en leveranciersafhankelijkheid willen vermijden
  • U moet bestaande systemen integreren en een gedeeld referentiearchitectuurmodel willen
  • Je opereert in een context meerdere landen (internationale samenwerking, grensoverschrijdende dienstverlening)
  • U wilt bijdragen aan het open source-ecosysteem van GovStack en profiteren van de implementaties van andere landen

GovStack is minder geschikt wanneer je zeer specifieke diensten hebt voor de Italiaanse context, wanneer systems bestaande (SPID, pagoPA) voldoen al aan uw vereisten zonder de overhead van extra abstractie, of wanneer u over beperkte middelen beschikt om de complexiteit van een gedistribueerde federatie te beheren.

Conclusie: de GovTech-serie in perspectief

Dit artikel sluit de GovTech-serie af die gewijd is aan de digitalisering van het openbaar bestuur. We verkenden het volledige ecosysteem: van publieke digitale infrastructuur (DPI) tot identiteit Europese Unie (eIDAS 2.0, EUDI Wallet), van de concrete implementatie van SPID en CIE tot gegevensbescherming (GDPR-by-Design), van toegankelijkheid (WCAG 2.1 AA) tot open data (DCAT-AP_IT, CKAN) tot internationale modulaire raamwerken (GovStack).

De rode draad is altijd dezelfde: publieke digitale diensten moeten dat zijn inclusief, veilig, interoperabel en herbruikbaar. GovStack, met zijn gestandaardiseerde Building Blocks, levert een gemeenschappelijk vocabulaire en een gedeeld architectonisch model om deze doelen op mondiale schaal te bereiken.

De volledige GovTech-serie

  • #00: Digitale publieke infrastructuur - Architectuur en bouwsteen
  • #01: eIDAS 2.0 en EUDI Wallet - Handleiding voor ontwikkelaars
  • #02: OpenID Connect voor overheidsidentiteit
  • #03: Open Data API-ontwerp - Publiceer en consumeer openbare gegevens
  • #04: GDPR-by-Design - Architectuurpatronen voor openbare diensten
  • #05: Toegankelijke gebruikersinterface voor de PA - WCAG 2.1 AA-implementatie
  • #06: API-integratie voor de overheid - SPID, CIE en IT Digital Services
  • #07: GovStack-bouwsteen - (dit artikel)