Ga naar hoofdinhoud

Webhooks & Notifications

Overview

OpenRegister delivers event notifications to external systems via a full-featured webhook infrastructure and to Nextcloud users via in-app notifications. The webhook system uses CloudEvents v1.0 as the standard payload format, supports configurable payload transformation via Twig mappings, HMAC signing for security, retry with exponential backoff, and multi-tenant isolation.

Tender demand: 51% of analyzed government tenders require notification capabilities.

Webhooks

Webhook Configuration

A webhook subscription is registered via the API and stored as a Webhook entity with 23 fields:

{
"url": "https://external.system.nl/api/events",
"events": ["nl.conduction.openregister.object.created", "nl.conduction.openregister.object.updated"],
"secret": "hmac-signing-secret",
"method": "POST",
"headers": { "X-Source": "openregister" },
"timeout": 30,
"retryPolicy": "exponential",
"register": "meldingen-register",
"schema": "meldingen"
}

Webhooks can be scoped to a specific register, schema, or object-level filter condition.

Payload Formats

Three payload strategies are applied in priority order:

PriorityStrategyDescription
1 (highest)Twig MappingPayload is transformed by a Mapping entity using Twig templates — arbitrary output format
2CloudEvents v1.0Standard CloudEvents envelope, recommended for interoperability
3StandardOpenRegister native JSON format

Twig Mapping enables delivering events in any format required by the subscriber — ZGW notifications, FHIR events, VNG Notificaties API format, Slack messages, etc. — without hardcoded format knowledge in OpenRegister.

HMAC Signing

Outgoing webhook requests are signed with HMAC-SHA256 using the configured secret:

X-Signature: sha256=<hex_signature>

Subscribers can verify authenticity by computing the same signature over the raw request body.

Retry Mechanism

Failed webhook deliveries are retried by WebhookRetryJob (runs every 5 minutes):

PolicyDescription
exponentialBackoff doubles with each attempt (1m, 2m, 4m, 8m, …)
linearFixed interval between attempts
fixedAll retries at the same interval

Maximum retry count and backoff ceiling are configurable per webhook.

Delivery Logging

Every delivery attempt is logged in WebhookLog:

  • HTTP status code and response body
  • Delivery timestamp and duration
  • Error message for failed deliveries
  • Retry count

Statistics are available via GET /api/webhooks/{id}/statistics.

Event Filtering

WebhookEventListener handles 36+ event types across 11 entity categories. Webhooks can filter by:

  • Event type (class name)
  • Register slug
  • Schema slug
  • Conditional property matching (object-level conditions)

Multi-Tenancy

Webhooks are scoped to organisations via MultiTenancyTrait on WebhookMapper. Each organisation's webhooks only receive events for objects in their organisation scope.

Management API

GET    /api/webhooks                         List all webhooks
POST /api/webhooks Create a new webhook
GET /api/webhooks/{id} Get a webhook
PUT /api/webhooks/{id} Update a webhook
DELETE /api/webhooks/{id} Delete a webhook
POST /api/webhooks/{id}/test Send a test event
GET /api/webhooks/{id}/logs List delivery logs
GET /api/webhooks/{id}/statistics Delivery statistics
POST /api/webhooks/{id}/retry/{logId} Manually retry a failed delivery
GET /api/webhooks/events List all available event types

In-App Notifications

OpenRegister integrates with Nextcloud's INotificationManager for user-facing in-app notifications:

Notification Channels

ChannelDescription
nc-notificationNextcloud notification bell (via INotificationManager)
emailVia IMailer (being replaced by n8n)
activityActivity stream entry per recipient
webhookInline POST per dispatch; with webhook.persistent: true an auto-managed Webhook entity routes through the standard retry / HMAC / dead-letter pipeline
talkPosts a chat message to the configured Spreed room (one-shot per dispatch, recipients are not @-mentioned)

Notification Rules

Rules live on the schema under configuration['x-openregister-notifications']. Each entry has trigger, recipients, channels, and a subject template (supports {{prop}} interpolation).

Triggers

trigger.typeFires when
createdObject created.
updatedObject updated.
transitionObject transitioned via the lifecycle state machine. Optional trigger.action filters to a specific action.
scheduledPeriodic. Requires trigger.intervalSec >= 60. The 60s ScheduledNotificationJob iterates the schema, optionally narrowed by trigger.filter (flat equality match on object data), and dispatches once per interval.
thresholdAggregation crossed a threshold. Requires trigger.aggregation (declared on the same schema), trigger.op[gt, gte, lt, lte, eq, ne], and trigger.value. AggregationThresholdListener re-runs the aggregation on object-write events and dispatches once per below→above transition.

Recipient kinds

kindResolution
usersLiteral list (recipient.users: [uid, …]).
fieldReads recipient.field from the object data, treats the value as a uid.
groupsMembers of recipient.groups.
relationResolves a typed x-openregister-relations field; reads either a uid string, an array of uids, or an array of objects with userId.
object-aclACL holders of the object for the configured recipient.permission[read, manage].
expressionArbitrary resolver class (DI tag in recipient.resolver). Must implement RecipientResolverInterface::resolve(ObjectEntity $object, array $context): string[].

Example

{
"x-openregister-notifications": {
"meetingReminderDaily": {
"trigger": {"type": "scheduled", "intervalSec": 86400, "filter": {"lifecycle": "scheduled"}},
"recipients": [{"kind": "field", "field": "chair"}],
"channels": ["nc-notification", "email"],
"subject": "Reminder: {{title}} starts soon"
},
"tooManyOverdue": {
"trigger": {"type": "threshold", "aggregation": "totalOverdue", "op": "gt", "value": 10},
"recipients": [{"kind": "groups", "groups": ["admin"]}],
"channels": ["nc-notification", "talk"],
"talk": {"token": "abc123def"},
"subject": "Action items overdue: {{value}}"
},
"meetingClosed": {
"trigger": {"type": "transition", "action": "close"},
"recipients": [{"kind": "object-acl", "permission": "manage"}],
"channels": ["webhook"],
"webhook": {
"persistent": true,
"url": "https://example.com/hooks/closed",
"events": ["ObjectTransitionedEvent"],
"secret": "..."
},
"subject": "Meeting closed"
}
}
}

VNG Notificaties API Compliance

For Dutch government interoperability, webhook payloads can be formatted according to the VNG Notificaties API standard via a Twig mapping configuration. This enables OpenRegister to act as a notificatiecomponent in a ZGW API landscape.

Standards

StandardRole
CloudEvents v1.0Default webhook payload envelope
HMAC-SHA256Webhook request signing
VNG Notificaties APIDutch government notification format (via Mapping)
Nextcloud INotificationManagerIn-app notification delivery