Skip to main content

Webhooks

OpenRegister includes a built-in webhook system that enables two powerful use cases: event notifications and business logic integration. Webhooks allow you to trigger external workflows and integrations whenever events occur within OpenRegister, enabling seamless integration with external systems and complex business rule validation.

img.png

Overview

Webhooks serve two primary purposes in OpenRegister:

  1. Event Notifications: Send CloudEvents to external systems when objects experience CRUD operations
  2. Business Logic Integration: Add complex business rules and decision-making to the internal flow of registers

OpenRegister's webhook system provides a robust, production-ready solution with automatic retries, event filtering, and comprehensive monitoring.

Use Case 1: Event Notifications

The simplest use case for webhooks is notifying external systems when objects are created, updated, or deleted. This enables real-time synchronization between OpenRegister and other applications.

Example: Pet Store Inventory Sync

Imagine a pet store using OpenRegister to manage its inventory. When a new cat is added to the store, the point-of-sale (POS) system needs to be notified so it can include the new product in its product list.

Configuration:

{
"name": "POS Inventory Sync",
"url": "https://pos-system.example.com/api/products",
"method": "POST",
"events": ["OCA\\OpenRegister\\Event\\ObjectCreatedEvent"],
"filters": {
"objectType": "object",
"schema": "pet-store-cat-schema"
},
"enabled": true
}

When a cat object is created, OpenRegister automatically sends a CloudEvent to the POS system, which then updates its product catalog in real-time.

Use Case 2: Business Logic and Decision Making

Webhooks can also be used to add complex business logic and decision-making to the internal flow of registers. This is particularly useful when validation rules are too complex for JSON schema validation.

Example: Complex Pet Store Rules

Consider a pet store with complex business rules:

  • Rule 1: Cannot create cats named "Felix" in the month of December
  • Rule 2: Cannot create cats named "Felix" when there are already 5 or more cats with that name in the store

These rules involve:

  • Date-based validation (checking if current month is December)
  • Aggregation logic (counting existing cats with the same name)
  • External data validation (potentially checking against external APIs)

JSON schema validation cannot handle this complexity. Instead, we can use webhooks to register to the object pre-create event and fire a synchronous webhook to an external tool like n8n that allows us to graphically build business logic flows.

Configuration:

{
"name": "Felix Cat Validation",
"url": "http://n8n:5678/webhook/felix-validation",
"method": "POST",
"events": ["OCA\\OpenRegister\\Event\\ObjectCreatingEvent"],
"filters": {
"objectType": "object",
"schema": "pet-store-cat-schema",
"data.@self.name": "Felix"
},
"enabled": true,
"configuration": {
"interceptRequests": true,
"async": false,
"processResponse": true,
"responseProcessing": {
"mergeStrategy": "merge"
}
}
}

n8n Workflow Capabilities:

The n8n workflow can perform complex operations that JSON schema cannot:

  1. Date/Time Logic: Check if current date is in December
  2. API Queries: Query OpenRegister API to count existing cats with name "Felix"
  3. External API Calls: Validate against external services (e.g., check if Felix is a registered trademark, verify against pet registry databases)
  4. Data Transformation: Transform or enrich the request data before validation
  5. Conditional Logic: Implement complex if/then/else rules
  6. Aggregations: Count, sum, or perform other aggregations on existing data

Response Handling:

The n8n workflow returns a response that OpenRegister uses to determine whether to continue:

  • Success Response: Object creation proceeds
  • Error Response: Object creation is blocked, error message shown to user

This enables powerful business logic that would be impossible to implement with JSON schema alone.

Key Features

  • Event-Driven Architecture: Subscribe to specific OpenRegister events
  • Automatic Retries: Configurable retry policies (exponential, linear, fixed)
  • Event Filtering: Filter events based on payload data
  • Secure: HMAC-SHA256 signature verification
  • Statistics: Track delivery success rates and last execution times
  • Custom Headers: Add authentication headers or custom metadata
  • Wildcard Support: Use wildcards to match multiple events

Requirements

  • Nextcloud 28 or higher
  • OpenRegister app installed and enabled
  • External system to receive webhooks (n8n, Windmill, custom endpoint)

Managing Webhooks

OpenRegister provides a RESTful API for managing webhooks.

img_1.png img_2.png img_3.png img_4.png

Creating a Webhook

curl -X POST http://localhost:8080/index.php/apps/openregister/api/webhooks \
-u 'admin:admin' \
-H 'Content-Type: application/json' \
-d '{
"name": "My Webhook",
"url": "https://my-server.com/webhook",
"method": "POST",
"events": [
"OCA\\OpenRegister\\Event\\ObjectCreatedEvent",
"OCA\\OpenRegister\\Event\\ObjectUpdatedEvent"
],
"enabled": true,
"timeout": 30,
"maxRetries": 3,
"retryPolicy": "exponential"
}'

Parameters

ParameterTypeRequiredDescription
namestringYesWebhook display name
urlstringYesTarget URL to call
methodstringNoHTTP method: GET, POST, PUT, PATCH, DELETE (default: POST)
eventsarrayNoEvent class names (empty = all events)
enabledbooleanNoEnable/disable webhook (default: true)
headersobjectNoCustom HTTP headers
secretstringNoSecret for HMAC signature
filtersobjectNoEvent payload filters
timeoutintegerNoRequest timeout in seconds (default: 30)
maxRetriesintegerNoMaximum retry attempts (default: 3)
retryPolicystringNoRetry policy: exponential, linear, fixed (default: exponential)

Listing Webhooks

curl -X GET http://localhost:8080/index.php/apps/openregister/api/webhooks \
-u 'admin:admin'

Getting a Webhook

curl -X GET http://localhost:8080/index.php/apps/openregister/api/webhooks/{id} \
-u 'admin:admin'

Updating a Webhook

curl -X PUT http://localhost:8080/index.php/apps/openregister/api/webhooks/{id} \
-u 'admin:admin' \
-H 'Content-Type: application/json' \
-d '{
"enabled": false
}'

Deleting a Webhook

curl -X DELETE http://localhost:8080/index.php/apps/openregister/api/webhooks/{id} \
-u 'admin:admin'

Testing a Webhook

curl -X POST http://localhost:8080/index.php/apps/openregister/api/webhooks/{id}/test \
-u 'admin:admin'

img_5.png

The test endpoint sends a test payload to verify webhook configuration. After testing, the webhook list automatically refreshes to show updated statistics (Last Triggered, Success Rate).

Listing Available Events

curl -X GET http://localhost:8080/index.php/apps/openregister/api/webhooks/events \
-u 'admin:admin'

Available Events

OpenRegister dispatches events for all entity lifecycle operations. Below is a complete list of available events.

Object Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\ObjectCreatedEventObject createdAfter new object is saved
OCA\OpenRegister\Event\ObjectUpdatedEventObject updatedAfter object is modified
OCA\OpenRegister\Event\ObjectDeletedEventObject deletedAfter object is removed
OCA\OpenRegister\Event\ObjectLockedEventObject lockedAfter object is locked
OCA\OpenRegister\Event\ObjectUnlockedEventObject unlockedAfter object is unlocked
OCA\OpenRegister\Event\ObjectRevertedEventObject revertedAfter object is reverted to previous version

Register Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\RegisterCreatedEventRegister createdAfter new register is saved
OCA\OpenRegister\Event\RegisterUpdatedEventRegister updatedAfter register is modified
OCA\OpenRegister\Event\RegisterDeletedEventRegister deletedAfter register is removed

Schema Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\SchemaCreatedEventSchema createdAfter new schema is saved
OCA\OpenRegister\Event\SchemaUpdatedEventSchema updatedAfter schema is modified
OCA\OpenRegister\Event\SchemaDeletedEventSchema deletedAfter schema is removed

Application Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\ApplicationCreatedEventApplication createdAfter new application is saved
OCA\OpenRegister\Event\ApplicationUpdatedEventApplication updatedAfter application is modified
OCA\OpenRegister\Event\ApplicationDeletedEventApplication deletedAfter application is removed

Agent Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\AgentCreatedEventAgent createdAfter new agent is saved
OCA\OpenRegister\Event\AgentUpdatedEventAgent updatedAfter agent is modified
OCA\OpenRegister\Event\AgentDeletedEventAgent deletedAfter agent is removed

Source Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\SourceCreatedEventSource createdAfter new source is saved
OCA\OpenRegister\Event\SourceUpdatedEventSource updatedAfter source is modified
OCA\OpenRegister\Event\SourceDeletedEventSource deletedAfter source is removed

Configuration Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\ConfigurationCreatedEventConfiguration createdAfter new configuration is saved
OCA\OpenRegister\Event\ConfigurationUpdatedEventConfiguration updatedAfter configuration is modified
OCA\OpenRegister\Event\ConfigurationDeletedEventConfiguration deletedAfter configuration is removed

View Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\ViewCreatedEventView createdAfter new view is saved
OCA\OpenRegister\Event\ViewUpdatedEventView updatedAfter view is modified
OCA\OpenRegister\Event\ViewDeletedEventView deletedAfter view is removed

Conversation Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\ConversationCreatedEventConversation createdAfter new conversation is saved
OCA\OpenRegister\Event\ConversationUpdatedEventConversation updatedAfter conversation is modified
OCA\OpenRegister\Event\ConversationDeletedEventConversation deletedAfter conversation is removed

Organisation Events

EventDescriptionWhen Triggered
OCA\OpenRegister\Event\OrganisationCreatedEventOrganisation createdAfter new organisation is saved
OCA\OpenRegister\Event\OrganisationUpdatedEventOrganisation updatedAfter organisation is modified
OCA\OpenRegister\Event\OrganisationDeletedEventOrganisation deletedAfter organisation is removed

Webhook Payload

All webhooks receive a standardized payload. The format depends on the HTTP method:

POST, PUT, PATCH, DELETE Methods

For these methods, the payload is sent as JSON in the request body:

{
"event": "OCA\\OpenRegister\\Event\\ObjectCreatedEvent",
"webhook": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "My Webhook"
},
"data": {
"objectType": "object",
"action": "created",
"object": {
"id": 123,
"uuid": "abc-123",
"...": "..."
},
"register": "my-register-uuid",
"schema": "my-schema-uuid"
},
"timestamp": "2025-11-20T20:00:00+00:00",
"attempt": 1
}

GET Method

For GET requests, the payload is sent as query parameters instead of a JSON body:

GET /webhook-endpoint?event=OCA%5COpenRegister%5CEvent%5CObjectCreatedEvent&webhook[id]=550e8400-e29b-41d4-a716-446655440000&webhook[name]=My%20Webhook&data[objectType]=object&data[action]=created&timestamp=2025-11-20T20:00:00%2B00:00&attempt=1

The query parameters are URL-encoded versions of the same payload structure.

Payload Structure

FieldTypeDescription
eventstringFull event class name
webhook.idstringWebhook UUID
webhook.namestringWebhook name
dataobjectEvent-specific data
timestampstringISO 8601 timestamp
attemptintegerDelivery attempt number (1 for first attempt)

Event Filtering

You can filter which events trigger webhooks using the filters parameter:

{
"filters": {
"objectType": "object",
"action": "created",
"register": "my-specific-register"
}
}

Filters support:

  • Exact matching: Filter value must match payload value exactly
  • Array matching: Payload value must be in the filter array
  • Dot notation: Access nested payload values (e.g., 'object.uuid')

Example with array filter:

{
"filters": {
"action": ["created", "updated"]
}
}

Security

HMAC Signature Verification

When you provide a secret during webhook creation, OpenRegister signs each payload with HMAC-SHA256 and includes the signature in the X-Webhook-Signature header.

To verify signatures:

import hmac
import hashlib
import json

def verify_webhook(payload_body, signature, secret):
expected = hmac.new(
secret.encode('utf-8'),
payload_body,
hashlib.sha256
).hexdigest()

return hmac.compare_digest(expected, signature)

# In your webhook endpoint
signature = request.headers.get('X-Webhook-Signature')
is_valid = verify_webhook(request.body, signature, 'your-secret')

Custom Headers

Add authentication headers or custom metadata:

{
"headers": {
"Authorization": "Bearer your-token",
"X-Custom-Header": "value"
}
}

Retry Policies

OpenRegister supports three retry policies:

Exponential Backoff (default)

Doubles the delay after each failure:

  • Attempt 1: Immediate
  • Attempt 2: 2 minutes
  • Attempt 3: 4 minutes
  • Attempt 4: 8 minutes

Linear Backoff

Increases delay linearly:

  • Attempt 1: Immediate
  • Attempt 2: 5 minutes
  • Attempt 3: 10 minutes
  • Attempt 4: 15 minutes

Fixed Delay

Constant delay between retries:

  • All retries: 5 minutes

Wildcard Events

Subscribe to multiple events using wildcards:

{
"events": [
"OCA\\OpenRegister\\Event\\Object*",
"OCA\\OpenRegister\\Event\\*CreatedEvent"
]
}

Or leave events empty to receive all events:

{
"events": []
}

Monitoring

Each webhook tracks delivery statistics:

MetricDescription
totalDeliveriesTotal delivery attempts
successfulDeliveriesSuccessful deliveries
failedDeliveriesFailed deliveries
lastTriggeredAtLast delivery attempt timestamp
lastSuccessAtLast successful delivery timestamp
lastFailureAtLast failed delivery timestamp

Integrations

See platform-specific integration guides:

Examples

Subscribe to All Object Events

{
"name": "Object Monitor",
"url": "https://my-server.com/webhooks/objects",
"events": ["OCA\\OpenRegister\\Event\\Object*"],
"enabled": true
}

Subscribe to Specific Register Changes

{
"name": "Register Monitor",
"url": "https://my-server.com/webhooks/register",
"events": [
"OCA\\OpenRegister\\Event\\RegisterCreatedEvent",
"OCA\\OpenRegister\\Event\\RegisterUpdatedEvent"
],
"filters": {
"register": "my-register-uuid"
},
"enabled": true
}

High Security Webhook with Custom Headers

{
"name": "Secure Webhook",
"url": "https://my-server.com/secure-webhook",
"secret": "my-super-secret-key",
"headers": {
"Authorization": "Bearer my-api-token",
"X-Source": "OpenRegister"
},
"events": [],
"enabled": true
}

Troubleshooting

Webhook Not Firing

  1. Check if webhook is enabled: GET /api/webhooks/{id}
  2. Verify events are being triggered (check logs)
  3. Ensure URL is accessible from Nextcloud container
  4. Check network connectivity and firewall rules

Delivery Failures

  1. Check webhook statistics for error patterns
  2. Review Nextcloud logs: docker logs nextcloud | grep 'Webhook'
  3. Test webhook manually: POST /api/webhooks/{id}/test
  4. Verify target endpoint is responding correctly

Debug Mode

Enable debug logging in Nextcloud:

// config/config.php
'loglevel' => 0, // 0 = debug, 4 = error

Then monitor logs:

docker logs -f nextcloud | grep -E 'Webhook|OpenRegister'

Best Practices

  1. Use Secrets: Always configure HMAC secrets for production webhooks
  2. Set Appropriate Timeouts: Balance between reliability and resource usage
  3. Filter Events: Only subscribe to events you need
  4. Monitor Statistics: Regularly check delivery success rates
  5. Handle Retries: Design endpoints to be idempotent
  6. Validate Signatures: Always verify HMAC signatures in production
  7. Use HTTPS: Secure webhook URLs with TLS encryption
  8. Test First: Use the test endpoint before going live

Request/Response Webhook Flow

OpenRegister supports intercepting requests before they reach controllers, sending them to external webhooks as CloudEvents, and optionally processing responses to modify the request. This enables powerful use cases like:

  • Request Validation: Validate requests before they're processed
  • Data Transformation: Transform or enrich request data
  • Formatting Logic: Apply formatting rules before object creation
  • External Processing: Send requests to external services for processing

What are CloudEvents?

CloudEvents is a specification for describing event data in a common way. It provides:

  • Consistent Structure: Standardized event format across systems
  • Interoperability: Works with different event systems and platforms
  • Standardized Metadata: Includes source, type, id, time, and other standard attributes
  • Extensibility: Supports custom attributes and data

CloudEvents makes it easier to integrate services that produce and consume events, enabling better interoperability between different systems.

Request Interception Flow

The following diagram illustrates how requests are intercepted and processed:

Configuration

To enable request interception, configure your webhook with the following settings:

{
"name": "Request Validator",
"url": "https://my-service.com/validate",
"method": "POST",
"events": ["OCA\\OpenRegister\\Event\\ObjectCreatingEvent"],
"enabled": true,
"configuration": {
"interceptRequests": true,
"async": false,
"processResponse": true,
"responseProcessing": {
"mergeStrategy": "merge",
"fieldMapping": {
"validatedData": "data",
"enrichedFields": "@self"
}
}
}
}

Configuration Options

OptionTypeDefaultDescription
interceptRequestsbooleanfalseEnable request interception for this webhook
asyncbooleanfalseIf true, don't wait for response (fire and forget)
processResponsebooleanfalseIf true, process webhook response to modify request
responseProcessing.mergeStrategystring"merge"How to merge response: "merge", "replace", or "custom"
responseProcessing.fieldMappingobjectCustom field mapping when strategy is "custom"

CloudEvent Payload Structure

When a request is intercepted, it's formatted as a CloudEvent with the following structure:

{
"specversion": "1.0",
"type": "object.creating",
"source": "https://nextcloud.example.com/apps/openregister",
"id": "550e8400-e29b-41d4-a716-446655440000",
"time": "2025-01-25T10:30:00+00:00",
"datacontenttype": "application/json",
"subject": "object:register-1/schema-2",
"data": {
"method": "POST",
"path": "/api/objects/register-1/schema-2",
"queryParams": {},
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer token"
},
"body": {
"@self": {
"title": "My Object",
"description": "Object description"
}
}
},
"openregister": {
"app": "openregister",
"version": "1.0.0"
}
}

Response Processing

When processResponse is enabled, the webhook response can modify the request:

Merge Strategy: Merges response data into request data

// Webhook Response
{
"data": {
"@self": {
"validated": true,
"enrichedField": "value"
}
}
}

// Result: Merged into original request

Replace Strategy: Replaces entire request body with response data

// Webhook Response
{
"data": {
"@self": {
"completely": "new",
"structure": "here"
}
}
}

// Result: Request body replaced

Custom Strategy: Uses field mapping to selectively update fields

// Configuration
{
"fieldMapping": {
"validatedData": "data",
"metadata": "@self.metadata"
}
}

// Webhook Response
{
"validatedData": {...},
"metadata": {...}
}

// Result: Mapped to specific request fields

Example Use Cases

1. Request Validation

Validate requests before processing:

{
"name": "Validation Webhook",
"url": "https://validator.example.com/validate",
"configuration": {
"interceptRequests": true,
"async": false,
"processResponse": true,
"responseProcessing": {
"mergeStrategy": "merge"
}
}
}

2. Data Enrichment

Enrich request data with external information:

{
"name": "Enrichment Webhook",
"url": "https://enricher.example.com/enrich",
"configuration": {
"interceptRequests": true,
"async": false,
"processResponse": true,
"responseProcessing": {
"mergeStrategy": "merge"
}
}
}

3. Async Notifications

Send notifications without waiting for response:

{
"name": "Notification Webhook",
"url": "https://notifier.example.com/notify",
"configuration": {
"interceptRequests": true,
"async": true,
"processResponse": false
}
}

Webhook Logging

All webhook delivery attempts are logged to the webhook_logs table, providing comprehensive audit trails and debugging capabilities.

Log Entry Structure

Each log entry contains:

FieldTypeDescription
webhookIdintegerReference to the webhook
eventClassstringEvent class name that triggered the webhook
payloadobjectFull payload sent to webhook
requestBodystringRequest body as JSON (only stored on failure for retry purposes)
urlstringTarget URL
methodstringHTTP method used
successbooleanWhether delivery succeeded
statusCodeintegerHTTP status code from response
responseBodystringResponse body from webhook
errorMessagestringError message if delivery failed
attemptintegerAttempt number (1 for first attempt)
nextRetryAtdatetimeTimestamp for next retry (if failed)
createddatetimeWhen the log entry was created

Note: The requestBody field is only stored when a webhook delivery fails. This allows the system to retry failed deliveries with the exact same payload. Successful deliveries do not store the request body to conserve database space.

Accessing Logs

Logs can be accessed via the API:

# Get logs for a specific webhook
curl -X GET http://localhost:8080/index.php/apps/openregister/api/webhooks/{id}/logs \
-u 'admin:admin'

# Get all webhook logs (with optional filtering)
curl -X GET 'http://localhost:8080/index.php/apps/openregister/api/webhooks/logs?webhook_id=1&limit=50&offset=0&success=false' \
-u 'admin:admin'

Logs API Parameters

ParameterTypeDescription
webhook_idintegerFilter logs by webhook ID (optional)
limitintegerMaximum number of logs to return (default: 50)
offsetintegerNumber of logs to skip (default: 0)
successbooleanFilter by success status: true or false (optional)

Retrying Failed Webhooks

You can manually retry a failed webhook delivery using the log entry ID:

curl -X POST http://localhost:8080/index.php/apps/openregister/api/webhooks/logs/{logId}/retry \
-u 'admin:admin'

This will retry the webhook delivery using the stored request payload from the original failed attempt.

Retry Mechanism

OpenRegister includes an automatic retry mechanism for failed webhook deliveries using Nextcloud background jobs with exponential backoff.

How It Works

  1. Initial Delivery: When a webhook delivery fails, a log entry is created with success=false
  2. Retry Scheduling: The system calculates the next retry time based on the retry policy and stores it in nextRetryAt
  3. Cron Job Processing: A background cron job (WebhookRetryJob) runs every 5 minutes
  4. Retry Execution: The cron job finds failed logs where nextRetryAt has passed and retries the delivery
  5. Exponential Backoff: Each retry uses increasing delays to avoid overwhelming the target service

Retry Policies

Three retry policies are supported:

Exponential Backoff (default)

Delays double with each attempt:

  • Attempt 1: Immediate
  • Attempt 2: 2 minutes (2^1 * 60)
  • Attempt 3: 4 minutes (2^2 * 60)
  • Attempt 4: 8 minutes (2^3 * 60)

Linear Backoff

Delays increase linearly:

  • Attempt 1: Immediate
  • Attempt 2: 5 minutes (1 * 300)
  • Attempt 3: 10 minutes (2 * 300)
  • Attempt 4: 15 minutes (3 * 300)

Fixed Delay

Constant delay between retries:

  • All retries: 5 minutes (300 seconds)

Retry Flow Diagram

Configuration

Retries are enabled webhook-wide:

{
"name": "My Webhook",
"maxRetries": 3,
"retryPolicy": "exponential",
"configuration": {
"enableRetries": true
}
}

Retry Configuration Options

OptionTypeDefaultDescription
maxRetriesinteger3Maximum number of retry attempts
retryPolicystring"exponential"Retry policy: "exponential", "linear", or "fixed"
configuration.enableRetriesbooleantrueEnable/disable retries for this webhook

Monitoring Retries

You can monitor retry status by checking webhook logs:

# Get statistics for a webhook
curl -X GET http://localhost:8080/index.php/apps/openregister/api/webhooks/{id}/logs/stats \
-u 'admin:admin'

Response:

{
"total": 150,
"successful": 145,
"failed": 5,
"pendingRetries": 2
}

API Reference

For complete API documentation, see the Events API Reference.