# Webhook Management Guide

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

## Prerequisites

Before enabling webhooks, ensure:

1. **Nextcloud 30+** with `webhook_listeners` app enabled
2. **[Astrolabe app](https://github.com/cbcoutinho/astrolabe)** installed in Nextcloud (provides settings UI and credentials API)
3. **MCP server** accessible from Nextcloud via HTTP(S)
4. **Vector sync enabled** on the MCP server

## Webhook Architecture Overview

The webhook system has two components:

1. **Webhook Registration** - Configuring Nextcloud to send change notifications to the MCP server
2. **Background Sync Credentials** - Allowing the MCP server to access Nextcloud APIs on behalf of users

Both must be configured for webhooks to function properly.

## Deployment Mode Specifics

### 1. Single-User BasicAuth

**Configuration:**
```bash
NEXTCLOUD_HOST=http://localhost:8080
NEXTCLOUD_USERNAME=admin
NEXTCLOUD_PASSWORD=password
VECTOR_SYNC_ENABLED=true
```

**Enable Webhooks:**
1. Register webhooks using occ commands (requires Nextcloud admin):
   ```bash
   # 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)
   ```

2. Optionally reduce polling frequency:
   ```bash
   VECTOR_SYNC_SCAN_INTERVAL=86400  # 24 hours
   ```

**Disable Webhooks:**
```bash
# 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

---

### 2. Multi-User BasicAuth Pass-Through

**Configuration:**
```bash
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**:

1. **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

2. **App Password** (for background sync):
   - Generated in Nextcloud Security settings
   - Stored encrypted in Nextcloud's `oc_preferences` via Astrolabe
   - Used by background scanners to access Nextcloud APIs

**Enable Webhooks:**

#### Step 1: Complete OAuth Login (for Management API)
Users must authorize the MCP server to access their Nextcloud:

1. Navigate to **Nextcloud Settings → Astrolabe** (Personal settings)
2. Click **"Authorize via OAuth"** under "Option 1"
3. Complete OAuth consent flow
4. Verify the page shows "Background Sync Access: Active"

#### Step 2: Configure App Password (for Background Sync)
Since OAuth refresh tokens have short expiry, users should also configure an app password:

1. Navigate to **Nextcloud Settings → Security**
2. Generate a new app password (name it "Astrolabe" or "MCP Server")
3. Return to **Nextcloud Settings → Astrolabe**
4. Under "Option 2: App Password", paste the app password
5. Click **Save**

#### Step 3: Register Webhooks (Admin)
Same as Single-User BasicAuth:
```bash
php occ webhook_listeners:add \
  --event "OCP\Files\Events\Node\NodeCreatedEvent" \
  --uri "http://mcp-server:8003/webhooks/nextcloud" \
  --method POST
```

**Disable Webhooks:**

*Per-User:*
1. Navigate to **Nextcloud Settings → Astrolabe**
2. Click **"Revoke Access"** (for OAuth tokens) or **"Revoke Access"** (for app password)

*System-Wide:*
```bash
php occ webhook_listeners:remove <webhook-id>
```

**Troubleshooting:**

If OAuth login fails with "Access forbidden - Your client is not authorized":
1. Check if OAuth client is registered:
   ```sql
   SELECT id, name, client_identifier FROM oc_oidc_clients
   WHERE dcr = 1 ORDER BY id DESC LIMIT 5;
   ```
2. Restart MCP server to trigger DCR re-registration
3. Verify `NEXTCLOUD_OIDC_CLIENT_ID` and `NEXTCLOUD_OIDC_CLIENT_SECRET` are set

If background sync fails with "User no longer provisioned":
1. Verify app password is stored:
   ```sql
   SELECT userid, configkey FROM oc_preferences
   WHERE appid = 'astrolabe' AND userid = 'username';
   ```
2. Ensure user completed **both** OAuth login AND app password setup

---

### 3. OAuth Single-Audience (Default OAuth Mode)

**Configuration:**
```bash
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=true
```

**Enable Webhooks:**

#### Step 1: User Provisioning
Users authorize via OAuth with `offline_access` scope:

1. MCP client initiates OAuth flow
2. User consents to requested scopes including `offline_access`
3. MCP server stores refresh token for background operations

Alternatively, via Astrolabe UI:
1. Navigate to **Nextcloud Settings → Astrolabe**
2. Click **"Authorize via OAuth"**
3. Complete consent flow

#### Step 2: Register Webhooks (Admin)
```bash
php occ webhook_listeners:add \
  --event "OCP\Files\Events\Node\NodeCreatedEvent" \
  --uri "http://mcp-server:8001/webhooks/nextcloud" \
  --method POST
```

**Disable Webhooks:**

*Per-User:*
- Via Astrolabe UI: Click "Disable Indexing" or "Disconnect"
- Via MCP tool: Use `revoke_nextcloud_access` if available

*System-Wide:*
```bash
php occ webhook_listeners:remove <webhook-id>
```

---

### 4. Smithery Stateless

**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.

---

## Webhook Event Types

Register these webhook events for full vector sync coverage:

### File/Note Events
```bash
# 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"
```

### Calendar Events
```bash
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"
```

### Tables Events
```bash
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"
```

## Security Considerations

### Webhook Authentication (required — GHSA-8vh3-g2qg-2h2c)
`WEBHOOK_SECRET` is **required** to use webhooks. The receiver trusts the
`user.uid` in the payload and feeds it to Qdrant, so an unauthenticated POST
could delete or re-index any user's embeddings. When `WEBHOOK_SECRET` is unset,
the `/webhooks/nextcloud` route is **not mounted** (404) and registration
refuses to create webhooks; vector sync still runs via the polling scanner.

```bash
# MCP Server (generate with: python -c "import secrets; print(secrets.token_urlsafe(32))")
WEBHOOK_SECRET=<generate-random-secret>

# Nextcloud webhook registration — the Authorization header is mandatory
php occ webhook_listeners:add \
  --event "..." \
  --uri "$MCP_URL/webhooks/nextcloud" \
  --header "Authorization: Bearer <secret>"
```

### Token Storage
- 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

## Monitoring

### MCP Server Logs
```bash
# 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 credentials
```

### Nextcloud Logs
```bash
docker compose exec app cat /var/www/html/data/nextcloud.log | \
  jq 'select(.message | contains("webhook"))' | tail
```

### Database Checks
```sql
-- 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';
```

## Common Issues

### "Access forbidden - Your client is not authorized to connect"
**Cause:** OAuth client registration expired or not present in Nextcloud
**Fix:** Restart MCP server to trigger DCR re-registration

### "User X no longer provisioned, stopping scanner"
**Cause:** Background sync credentials missing or expired
**Fix:** User must complete credential provisioning (see mode-specific steps)

### "Failed to fetch" in browser console during OAuth
**Cause:** Network issue between browser and MCP server callback endpoint
**Fix:** Verify MCP server is accessible at the configured `NEXTCLOUD_MCP_SERVER_URL`

### Webhooks not firing
**Causes:**
1. `webhook_listeners` app not enabled
2. Webhook not registered for the event type
3. Background job workers not running
**Fix:**
```bash
php occ app:enable webhook_listeners
php occ background:cron  # or configure systemd cron
```
