| name | ls-notif |
| description | NL GOV CloudEvents profiel voor notificatieservices: abonnementen, webhooks, pub/sub tussen overheidsorganisaties. |
| model | sonnet |
| allowed-tools | ["Bash(gh api *)","Bash(gh issue list *)","Bash(gh pr list *)","Bash(gh search *)","Bash(curl -s *)","WebFetch(*)"] |
| metadata | {"created-with-ai":"true","created-with-model":"claude-opus-4-20250514","created-date":"2025-02-12","status":"concept"} |
CONCEPT — Let op: Deze skill is geen officieel product van Logius. De beschrijvingen zijn informatieve samenvattingen — niet de officiële standaarden zelf. De definities op forumstandaardisatie.nl en Logius zijn altijd leidend. Overheidsorganisaties die generatieve AI inzetten dienen te voldoen aan het Overheidsbreed standpunt voor de inzet van generatieve AI. Zie DISCLAIMER.md en onze verantwoording.
CloudEvents & Notificaties
Agent-instructie: Deze skill helpt bij het implementeren van event-driven communicatie met CloudEvents NL GOV profiel. Gebruik de cloudevents SDK voor Python/JavaScript. Source moet URN nld-notatie gebruiken: urn:nld:oin:<OIN>:systeem:<naam>.
De notificatiestandaarden definiëren hoe overheidsorganisaties gebeurtenissen (events) kunnen publiceren en daarop kunnen abonneren. Gebaseerd op de internationale CNCF CloudEvents specificatie (v1.0.1) met een NL GOV profiel dat specifieke eisen stelt aan naamgeving, identificatie en het gebruik van context-attributen binnen de Nederlandse overheid.
Versiemodel
Net als andere Logius-standaarden kennen de notificatiestandaarden twee publicatiekanalen (vergelijkbaar met W3C):
- Vastgestelde versie (DEF): officieel goedgekeurd, gepubliceerd op
gitdocumentatie.logius.nl
- Werkversie (draft): werk-in-uitvoering, gepubliceerd op
logius-standaarden.github.io
Het NL GOV profiel voor CloudEvents heeft een vastgestelde versie (v1.1 DEF). De Guidelines zijn een toelichting zonder eigen publicatie. Notificatieservices is een werkrepository zonder publicatie. Abonneren heeft een werkversie (v0.0.1 WV) op gitdocumentatie maar is nog niet vastgesteld.
Op het Forum Standaardisatie staat CloudEvents v1.0 als verplicht ('pas-toe-of-leg-uit', goedgekeurd OBDO 25-11-2025). Versie v1.1 is de meest recente DEF van Logius.
Repositories
¹ Abonneren WV v0.0.1 is gepubliceerd op gitdocumentatie.logius.nl, wat normaal voor vastgestelde versies (DEF/VV) is. Dit is een uitzondering; de standaard is nog in ontwikkeling.
CloudEvents NL GOV Profiel
Het NL GOV profiel scherpt de CNCF CloudEvents specificatie aan voor gebruik binnen de Nederlandse overheid. Hieronder de volledige set verplichte en optionele attributen.
Verplichte attributen
| Attribuut | Type | Beschrijving |
|---|
id | String | Unieke event-identifier. Bij voorkeur een persistent (domeinspecifiek) ID, bijvoorbeeld doc2021033441. Wanneer geen persistent ID beschikbaar is, gebruik een UUID (v4). De combinatie source + id moet globaal uniek zijn. |
source | URI-reference | Context waarin het event is ontstaan. Het NL GOV profiel schrijft URN-notatie voor met de nld namespace. Formaat: urn:nld:oin:<OIN>:systeem:<systeemnaam>. Voorbeeld: urn:nld:oin:00000001823288444000:systeem:BRP-component. |
specversion | String | De versie van de CloudEvents specificatie. Moet "1.0" zijn. |
type | String | Het type event in Reverse Domain Name Notation. Voorbeeld: nl.brp.persoon-verhuisd. Versioning gebeurt met een v prefix: nl.brp.verhuizing.v2. Types worden geregistreerd in een centraal register. |
Optionele attributen
| Attribuut | Type | Beschrijving |
|---|
datacontenttype | String | Media type van de event data payload. JSON (application/json) wordt aanbevolen als standaardformaat. |
dataschema | URI | Verwijzing naar het schema waaraan de event data voldoet. Maakt validatie door ontvangende partijen mogelijk. |
subject | String | Het onderwerp waarop het event betrekking heeft, bijvoorbeeld een BSN (999990342) of zaak-ID. Hiermee kunnen afnemers filteren zonder de payload te openen. |
time | Timestamp | Tijdstip in RFC 3339 formaat. Let op: dit is het tijdstip van logging/registratie van het event, niet noodzakelijk het moment van de werkelijke gebeurtenis. Voorbeeld: 2024-01-15T10:30:00Z. |
dataref | URI | Verwijzing naar een externe locatie waar de volledige event payload beschikbaar is. Implementeert het Claim Check Pattern voor grote payloads die niet in het event zelf passen. |
sequence | String | Geeft de relatieve volgorde van events aan. Nuttig wanneer de volgorde van verwerking van belang is voor de afnemer. Dit is een CloudEvents extensie-attribuut (CNCF Sequence-extensie, niet in de kernspec) dat het NL GOV profiel overneemt. |
sequencetype | String | Geeft het type sequentie aan (bijv. Integer). Optioneel; indien aanwezig MOET het een niet-lege string zijn. Dit is een CloudEvents extensie-attribuut (CNCF Sequence-extensie) dat het NL GOV profiel overneemt. |
Notificatieservices API
De Notificatieservices API is gespecificeerd als OpenAPI 3.x en biedt een centraal endpoint voor het publiceren van events.
Event publiceren
POST /api/v1/notifications
Authorization: Bearer <JWT>
Content-Type: application/json
{
"id": "f3dce042-cd6e-4977-844d-05be8dce7cea",
"source": "urn:nld:oin:00000001823288444000:systeem:BRP-component",
"specversion": "1.0",
"type": "nl.brp.persoon-verhuisd",
"subject": "999990342",
"time": "2024-01-15T10:30:00Z",
"datacontenttype": "application/json",
"data": {
"oud_adres": "Keizersgracht 100, Amsterdam",
"nieuw_adres": "Herengracht 200, Amsterdam"
}
}
Abonneren
Het Abonneren-component beschrijft hoe afnemers zich kunnen registreren om specifieke events te ontvangen. Twee hoofdmodellen worden onderscheiden:
Push-model (aanbevolen): Event-driven aflevering via webhooks. De notificatieservice stuurt events actief naar een door de afnemer opgegeven endpoint. Dit is het voorkeursmodel vanwege lage latency en efficiënter resourcegebruik.
Pull-model: Polling-gebaseerde aflevering. De afnemer bevraagt periodiek de notificatieservice op nieuwe events. Geschikt voor situaties waar de afnemer geen inkomend endpoint kan aanbieden (bijv. vanwege firewallrestricties).
Let op: De huidige Notificatieservices OpenAPI-specificatie bevat alleen push-endpoints voor events (POST /events). Er zijn geen pull/polling-endpoints voor het ophalen van events. De Abonneren-werkversie beschrijft push als het voorkeursmodel; pull/polling wordt alleen genoemd als het onwenselijke alternatief dat de abonnementsfunctie overbodig maakt — het is niet uitgewerkt als een ondersteund aflevermodel.
Keuzehulp: Push of Pull?
Kan de afnemer een bereikbaar HTTPS endpoint aanbieden?
JA → Push-model (webhook) — aanbevolen, lage latency
NEE → Pull-model (polling) — nog niet gespecificeerd in de API, check status in de Abonneren-repo
Security & Privacy
Bij het werken met notificaties gelden strikte eisen op het gebied van beveiliging en privacy:
- Geen gevoelige data in context-attributen: Context-attributen (zoals
source, type, subject) zijn inspecteerbaar en worden gelogd door tussenliggende systemen. Plaats nooit persoonsgegevens of gevoelige informatie in deze velden. Gebruik hiervoor de versleutelde data-payload.
- Versleuteling van event data: Wanneer de payload gevoelige gegevens bevat, moet deze versleuteld worden (end-to-end encryptie). De context-attributen blijven leesbaar voor routering.
- TLS verplicht: Alle communicatie tussen bronnen, notificatieservices en afnemers moet plaatsvinden over TLS (minimaal versie 1.2).
- Claim Check Pattern: Bij grote of gevoelige payloads kan het
dataref-attribuut verwijzen naar een beveiligd endpoint. De afnemer haalt de payload separaat op met eigen autorisatie.
Implementatievoorbeelden
Event Publiceren met CloudEvents SDK (Python)
from cloudevents.http import CloudEvent
from cloudevents.conversion import to_json
import requests, uuid
from datetime import datetime, timezone
event = CloudEvent({
"id": str(uuid.uuid4()),
"source": "urn:nld:oin:00000001823288444000:systeem:BRP-component",
"type": "nl.brp.persoon-verhuisd",
"specversion": "1.0",
"subject": "999990342",
"time": datetime.now(timezone.utc).isoformat(),
"datacontenttype": "application/json",
}, data={
"oud_adres": {"straat": "Keizersgracht", "huisnummer": "100", "plaats": "Amsterdam"},
"nieuw_adres": {"straat": "Herengracht", "huisnummer": "200", "plaats": "Amsterdam"},
})
response = requests.post(
"https://notificaties.example.com/api/v1/notifications",
headers={
"Authorization": "Bearer eyJ...",
"Content-Type": "application/cloudevents+json",
},
data=to_json(event),
)
print(f"Status: {response.status_code}")
Webhook Ontvanger (FastAPI)
from fastapi import FastAPI, Request, HTTPException
from cloudevents.http import from_http
app = FastAPI(title="CloudEvents Webhook Ontvanger")
@app.post("/webhooks/cloudevents")
async def receive_event(request: Request):
"""Ontvang en verwerk CloudEvents conform NL GOV profiel."""
body = await request.body()
headers = dict(request.headers)
try:
event = from_http(headers, body)
except Exception:
raise HTTPException(400, "Ongeldig CloudEvent formaat")
assert event["specversion"] == "1.0"
assert event["source"].startswith("urn:nld:")
assert "." in event["type"]
match event["type"]:
case "nl.brp.persoon-verhuisd":
await verwerk_verhuizing(event)
case "nl.kvk.inschrijving-gewijzigd":
await verwerk_kvk_wijziging(event)
case _:
print(f"Onbekend event type: {event['type']}")
return {"status": "accepted", "event_id": event["id"]}
async def verwerk_verhuizing(event):
"""Verwerk een BRP verhuizing notificatie."""
subject = event["subject"]
data = event.data
print(f"Persoon {subject} verhuisd naar {data['nieuw_adres']}")
Event Publiceren met curl
curl -X POST https://notificaties.example.com/api/v1/notifications \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/cloudevents+json" \
-d '{
"specversion": "1.0",
"id": "f3dce042-cd6e-4977-844d-05be8dce7cea",
"source": "urn:nld:oin:00000001823288444000:systeem:BRP-component",
"type": "nl.brp.persoon-verhuisd",
"subject": "999990342",
"time": "2024-01-15T10:30:00Z",
"datacontenttype": "application/json",
"data": {"nieuw_adres": "Herengracht 200, Amsterdam"}
}'
curl -X POST https://notificaties.example.com/api/v1/notifications \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-H "ce-specversion: 1.0" \
-H "ce-id: f3dce042-cd6e-4977-844d-05be8dce7cea" \
-H "ce-source: urn:nld:oin:00000001823288444000:systeem:BRP-component" \
-H "ce-type: nl.brp.persoon-verhuisd" \
-H "ce-subject: 999990342" \
-H "ce-time: 2024-01-15T10:30:00Z" \
-d '{"nieuw_adres": "Herengracht 200, Amsterdam"}'
Express.js Webhook Ontvanger
const express = require('express');
const { HTTP } = require('cloudevents');
const app = express();
app.use(express.json());
app.post('/webhooks/cloudevents', (req, res) => {
try {
const event = HTTP.toEvent({ headers: req.headers, body: req.body });
if (!event.source.startsWith('urn:nld:')) {
return res.status(400).json({ error: 'Source moet URN nld namespace gebruiken' });
}
console.log(`Event ontvangen: ${event.type} van ${event.source}`);
console.log(`Subject: ${event.subject}, Data:`, event.data);
switch (event.type) {
case 'nl.brp.persoon-verhuisd':
handleVerhuizing(event);
break;
default:
console.log(`Onbekend type: ${event.type}`);
}
res.status(200).json({ status: 'accepted', event_id: event.id });
} catch (err) {
res.status(400).json({ error: 'Ongeldig CloudEvent' });
}
});
app.listen(8080, () => console.log('Webhook listener op poort 8080'));
Claim Check Pattern voor Grote Payloads
event = CloudEvent({
"id": str(uuid.uuid4()),
"source": "urn:nld:oin:00000001823288444000:systeem:DMS",
"type": "nl.dms.document-beschikbaar",
"specversion": "1.0",
"subject": "doc-2024-001",
"dataref": "https://dms.example.com/api/v1/documents/doc-2024-001",
})
document = requests.get(
event["dataref"],
headers={"Authorization": "Bearer receiver_token"},
cert=("pkio_cert.pem", "pkio_key.pem"),
)
Foutafhandeling
Notificatieservice Error Responses
| Status | Beschrijving | Actie |
|---|
| 200 | Event succesvol gepubliceerd | - |
| 400 | Ongeldig CloudEvent (verplicht attribuut ontbreekt) | Corrigeer event en probeer opnieuw |
| 401 | JWT token ongeldig of verlopen | Vernieuw token |
| 403 | Scope notifications.distribute ontbreekt | Vraag juiste scope aan |
| 429 | Rate limit bereikt | Wacht en probeer opnieuw (exponential backoff) |
| 500 | Interne fout notificatieservice | Probeer opnieuw na 30 seconden |
Webhook Delivery Retry Patroon
Bij het afleveren van events aan webhook endpoints geldt een retry-strategie:
import time
def deliver_event(webhook_url: str, event: dict, max_retries: int = 5):
"""Lever event af aan webhook met exponential backoff."""
for attempt in range(max_retries):
try:
response = requests.post(webhook_url, json=event, timeout=10)
if response.status_code == 200:
return True
if response.status_code >= 500:
wait = min(2 ** attempt * 5, 300)
time.sleep(wait)
continue
if response.status_code >= 400:
log.error(f"Webhook afgewezen: {response.status_code}")
return False
except requests.Timeout:
wait = min(2 ** attempt * 5, 300)
time.sleep(wait)
return False
Achtergrondinfo
Zie reference.md voor het type systeem, kernconcepten, en aanbevolen technologieën.
Zie conflicts.md voor bronconflicten en gemaakte keuzes.