# ADR-0007: Wiring OpenZaak → Open Notificaties (NRC) for notifications - **Status:** Accepted - **Date:** 2026-06-29 - **Deciders:** Respellion engineering - **Relates to:** S-01-c (#56); completes S-01 (#2); unblocks the Event Subscriber (#7); builds on ADR-0002 (catalogus/seed) and ADR-0006 (runner-safe container harnesses) ## Context S-01 brought OpenZaak + Open Notificaties (NRC) up in compose but **deferred the notification wiring**: OpenZaak ran with `NOTIFICATIONS_DISABLED=true` and NRC's `setup_configuration` was empty. The walking skeleton (PRD §12) needs the upstream event path — a zaak created in OpenZaak must publish a notification NRC fans out to subscribers — before the Event Subscriber (#7) can consume it. The OpenZaak↔NRC handshake is intricate and several details are non-obvious; they were nailed down by iterating `setup_configuration` against the running stack. ## Decision **Provision both sides declaratively via `setup_configuration`, authenticate with the existing `big-reference-seed` client, and run NRC's celery-beat so deliveries happen.** - **OpenZaak** (`infra/openzaak/setup_configuration/data.yaml`): a `zgw_consumers` service `nrc` (api_type `nrc`, the NRC API root) plus `notifications_config` naming it. `NOTIFICATIONS_DISABLED` is flipped to `false` **only when NRC is present** — the full stack and the local twin set it; OpenZaak-only bring-ups (`openzaak-up`, the ACL integration test) default it back to `true` via `OZ_NOTIFICATIONS_DISABLED` so they don't 500 publishing to an absent NRC. - **NRC** (`infra/opennotificaties/setup_configuration/data.yaml`): the `big-reference-seed` JWT credential (to verify OpenZaak's token), a `zgw_consumers` `ac` service pointing at **OpenZaak's Autorisaties API**, the `autorisaties_api` step delegating authorization to that AC, and the `zaken` kanaal. NRC's init container switches from `migrate` to `/setup_configuration.sh`; its data.yaml is delivered through the `rr-nrc-config` external volume by `infra/seed-config.sh` (the same `docker cp` pattern as OpenZaak — bind mounts don't reach the CI runner's daemon). - **celery-beat is required.** NRC accepts a notification and writes a `ScheduledNotification`; a periodic `execute_notifications` task (celery-beat, every `NOTIFICATION_SEC_INTERVAL`s) drains it to the worker for delivery. The lean S-01 stack dropped beat — so notifications were accepted but never delivered. An `nrc-beat` service is added to every compose; the interval is lowered to 5s. Verification is a runner-safe smoke (`make verify-notifications`, `infra/verify-notifications.sh` + a `notifications` CI job): it brings the stack up, seeds a published BIG zaaktype, registers an abonnement to a webhook sink, creates a zaak, and asserts the sink receives the `zaken`/`create` notification — all from containers **inside** the compose network (ADR-0006). ## Consequences - **Positive:** the walking-skeleton event path works end to end; #7 can consume real notifications; the wiring is declarative and reproducible from a fresh `make`. - **Gotchas captured (see gitea-actions-gotchas.md):** - **Single-label hosts aren't URL-valid.** OpenZaak/NRC reject `http://openzaak…` /`http://nrc-web…` in URLs they validate (Django `URLValidator`); the verify harness reaches services and registers the sink callback **by container IP**. - **Abonnement callbacks must enforce auth.** NRC probes the callback during registration and refuses it (`no-auth-on-callback-url`) unless it returns 401 without the configured `Authorization`; the sink enforces a bearer token. - **Cost:** an extra long-running service (`nrc-beat`) per stack, and the verify job needs egress (base images + `selectielijst.openzaak.nl`, since the published zaaktype the check creates a zaak against depends on it — ADR-0006). - **Dev-only credentials** reused (`big-reference-seed` / its secret) across publish, AC lookup, and seeding — acceptable for the reference app, not production. ## Alternatives considered - **NRC with its own (non-AC) authorization** — rejected: delegating to OpenZaak's Autorisaties API is the upstream-intended model and reuses the applicatie that already grants `heeft_alle_autorisaties`. - **Keep beat out, deliver synchronously** — not an option: Open Notificaties 1.16 delivers via scheduled notifications drained by beat; there is no sync path. - **A persistent abonnement in `setup_configuration`** instead of registering one in the verify harness — deferred: the real subscriber is #7; the harness's sink abonnement is throwaway and IP-specific.