This guide explains how to enable and disable webhooks for vector sync in each MCP server deployment mode. Webhooks enable near-real-time synchronization of content changes to the vector database, complementing the default polling-based sync.
Related ADRs:
- ADR-010: Webhook-Based Vector Sync
- ADR-020: Deployment Modes and Configuration Validation
Before enabling webhooks, ensure:
- Nextcloud 30+ with
webhook_listenersapp enabled - Astrolabe app installed in Nextcloud (provides settings UI and credentials API)
- MCP server accessible from Nextcloud via HTTP(S)
- Vector sync enabled on the MCP server
The webhook system has two components:
- Webhook Registration - Configuring Nextcloud to send change notifications to the MCP server
- Background Sync Credentials - Allowing the MCP server to access Nextcloud APIs on behalf of users
Both must be configured for webhooks to function properly.
Configuration:
NEXTCLOUD_HOST=http://localhost:8080
NEXTCLOUD_USERNAME=admin
NEXTCLOUD_PASSWORD=password
VECTOR_SYNC_ENABLED=trueEnable Webhooks:
-
Register webhooks using occ commands (requires Nextcloud admin):
# Enable webhook_listeners app php occ app:enable webhook_listeners # Register webhooks for vector sync php occ webhook_listeners:add \ --event "OCP\Files\Events\Node\NodeCreatedEvent" \ --uri "http://mcp-server:8000/webhooks/nextcloud" \ --method POST # Repeat for other events (see Event Types below)
-
Optionally reduce polling frequency:
VECTOR_SYNC_SCAN_INTERVAL=86400 # 24 hours
Disable Webhooks:
# List registered webhooks
php occ webhook_listeners:list
# Remove specific webhook by ID
php occ webhook_listeners:remove <webhook-id>Notes:
- Simplest mode - admin credentials used for all operations
- No per-user provisioning required
- Background sync runs as the configured admin user
Configuration:
NEXTCLOUD_HOST=http://nextcloud.example.com
MCP_DEPLOYMENT_MODE=multi_user_basic
ENABLE_BACKGROUND_OPERATIONS=true
TOKEN_ENCRYPTION_KEY=<key>
TOKEN_STORAGE_DB=/app/data/tokens.db
VECTOR_SYNC_ENABLED=true
# OAuth client for Astrolabe API access
NEXTCLOUD_OIDC_CLIENT_ID=<client-id>
NEXTCLOUD_OIDC_CLIENT_SECRET=<client-secret>Credential Architecture: This mode uses two separate credential mechanisms:
-
OAuth Session (for management API access, including webhooks):
- Obtained via browser OAuth flow (
/oauth/login) - Stores refresh token in MCP server's
tokens.db - Used for webhook registration/management APIs
- Obtained via browser OAuth flow (
-
App Password (for background sync):
- Generated in Nextcloud Security settings
- Stored encrypted in Nextcloud's
oc_preferencesvia Astrolabe - Used by background scanners to access Nextcloud APIs
Enable Webhooks:
Users must authorize the MCP server to access their Nextcloud:
- Navigate to Nextcloud Settings → Astrolabe (Personal settings)
- Click "Authorize via OAuth" under "Option 1"
- Complete OAuth consent flow
- Verify the page shows "Background Sync Access: Active"
Since OAuth refresh tokens have short expiry, users should also configure an app password:
- Navigate to Nextcloud Settings → Security
- Generate a new app password (name it "Astrolabe" or "MCP Server")
- Return to Nextcloud Settings → Astrolabe
- Under "Option 2: App Password", paste the app password
- Click Save
Same as Single-User BasicAuth:
php occ webhook_listeners:add \
--event "OCP\Files\Events\Node\NodeCreatedEvent" \
--uri "http://mcp-server:8003/webhooks/nextcloud" \
--method POSTDisable Webhooks:
Per-User:
- Navigate to Nextcloud Settings → Astrolabe
- Click "Revoke Access" (for OAuth tokens) or "Revoke Access" (for app password)
System-Wide:
php occ webhook_listeners:remove <webhook-id>Troubleshooting:
If OAuth login fails with "Access forbidden - Your client is not authorized":
- Check if OAuth client is registered:
SELECT id, name, client_identifier FROM oc_oidc_clients WHERE dcr = 1 ORDER BY id DESC LIMIT 5;
- Restart MCP server to trigger DCR re-registration
- Verify
NEXTCLOUD_OIDC_CLIENT_IDandNEXTCLOUD_OIDC_CLIENT_SECRETare set
If background sync fails with "User no longer provisioned":
- Verify app password is stored:
SELECT userid, configkey FROM oc_preferences WHERE appid = 'astrolabe' AND userid = 'username';
- Ensure user completed both OAuth login AND app password setup
Configuration:
NEXTCLOUD_HOST=http://nextcloud.example.com
# No NEXTCLOUD_USERNAME/PASSWORD
ENABLE_BACKGROUND_OPERATIONS=true
TOKEN_ENCRYPTION_KEY=<key>
TOKEN_STORAGE_DB=/app/data/tokens.db
VECTOR_SYNC_ENABLED=trueEnable Webhooks:
Users authorize via OAuth with offline_access scope:
- MCP client initiates OAuth flow
- User consents to requested scopes including
offline_access - MCP server stores refresh token for background operations
Alternatively, via Astrolabe UI:
- Navigate to Nextcloud Settings → Astrolabe
- Click "Authorize via OAuth"
- Complete consent flow
php occ webhook_listeners:add \
--event "OCP\Files\Events\Node\NodeCreatedEvent" \
--uri "http://mcp-server:8001/webhooks/nextcloud" \
--method POSTDisable Webhooks:
Per-User:
- Via Astrolabe UI: Click "Disable Indexing" or "Disconnect"
- Via MCP tool: Use
revoke_nextcloud_accessif available
System-Wide:
php occ webhook_listeners:remove <webhook-id>Configuration:
NEXTCLOUD_HOST=http://nextcloud.example.com
ENABLE_TOKEN_EXCHANGE=true
ENABLE_BACKGROUND_OPERATIONS=true
TOKEN_ENCRYPTION_KEY=<key>
TOKEN_STORAGE_DB=/app/data/tokens.db
VECTOR_SYNC_ENABLED=trueEnable/Disable Webhooks: Same process as OAuth Single-Audience. The token exchange happens transparently when the MCP server accesses Nextcloud APIs.
Configuration:
- Configuration from session URL params
VECTOR_SYNC_ENABLED=false(required)
Webhooks: Not supported. This mode is stateless with no persistent storage or background operations.
Register these webhook events for full vector sync coverage:
# Use BeforeNodeDeletedEvent for deletions (includes node.id)
php occ webhook_listeners:add --event "OCP\Files\Events\Node\NodeCreatedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCP\Files\Events\Node\NodeWrittenEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCP\Files\Events\Node\BeforeNodeDeletedEvent" --uri "$MCP_URL/webhooks/nextcloud"php occ webhook_listeners:add --event "OCP\Calendar\Events\CalendarObjectCreatedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCP\Calendar\Events\CalendarObjectUpdatedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCP\Calendar\Events\CalendarObjectDeletedEvent" --uri "$MCP_URL/webhooks/nextcloud"php occ webhook_listeners:add --event "OCA\Tables\Event\RowAddedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCA\Tables\Event\RowUpdatedEvent" --uri "$MCP_URL/webhooks/nextcloud"
php occ webhook_listeners:add --event "OCA\Tables\Event\RowDeletedEvent" --uri "$MCP_URL/webhooks/nextcloud"Configure WEBHOOK_SECRET to require authentication for incoming webhooks:
# MCP Server
WEBHOOK_SECRET=<generate-random-secret>
# Nextcloud webhook registration
php occ webhook_listeners:add \
--event "..." \
--uri "$MCP_URL/webhooks/nextcloud" \
--header "Authorization: Bearer <secret>"- Refresh tokens and app passwords are encrypted using
TOKEN_ENCRYPTION_KEY - Store the key securely (environment variable, secrets manager)
- Different users have isolated credential storage
# Docker
docker compose logs mcp-multi-user-basic | grep -i webhook
# Key log messages
# - "Queued document from webhook: ..." - Success
# - "Webhook authentication failed" - Auth error
# - "User X no longer provisioned" - Missing credentialsdocker compose exec app cat /var/www/html/data/nextcloud.log | \
jq 'select(.message | contains("webhook"))' | tail-- Check registered webhooks
SELECT * FROM oc_webhook_listeners;
-- Check OAuth clients
SELECT id, name, token_type FROM oc_oidc_clients WHERE dcr = 1;
-- Check user credentials stored by Astrolabe app
SELECT userid, configkey FROM oc_preferences WHERE appid = 'astrolabe';Cause: OAuth client registration expired or not present in Nextcloud Fix: Restart MCP server to trigger DCR re-registration
Cause: Background sync credentials missing or expired Fix: User must complete credential provisioning (see mode-specific steps)
Cause: Network issue between browser and MCP server callback endpoint
Fix: Verify MCP server is accessible at the configured NEXTCLOUD_MCP_SERVER_URL
Causes:
webhook_listenersapp not enabled- Webhook not registered for the event type
- Background job workers not running Fix:
php occ app:enable webhook_listeners
php occ background:cron # or configure systemd cron