Agent skill
Nextcloud Webhooks Skill
Receive and authenticate Nextcloud webhooks. Use when setting up a webhook endpoint for the Nextcloud webhook_listeners app, checking the secret header added with authMethod "header", debugging 401s, or handling events like NodeCreatedEvent, NodeWrittenEvent, NodeDeletedEvent and CalendarObjectCreatedEvent.
Install this skill
npx skills add hookdeck/webhook-skills --skill nextcloud-webhooks
The bundled webhook_listeners app makes a self-hosted Nextcloud server call your endpoint when something happens: a file is created, written or deleted, a calendar object changes, a form is submitted. An admin registers each listener through the OCS API with an HTTP method, a URI, an event class and an optional secret header. Requests are not signed; you authenticate them with the header you chose.
When to Use This Skill
- How do I receive Nextcloud webhooks?
- How do I authenticate a Nextcloud
webhook_listenersrequest? (There is no signature) - How do I use
authMethod: "header"andauthDatato add a secret header? - How do I handle
NodeCreatedEvent,NodeWrittenEvent,NodeDeletedEventorCalendarObjectCreatedEvent? - Why does my Nextcloud webhook endpoint return 401 or 405?
Verification (core)
const crypto = require('crypto');
// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
const ha = crypto.createHash('sha256').update(String(a)).digest();
const hb = crypto.createHash('sha256').update(String(b)).digest();
return crypto.timingSafeEqual(ha, hb);
}
function verifyNextcloudRequest(headers,
secret = process.env.NEXTCLOUD_WEBHOOK_SECRET,
headerName = process.env.NEXTCLOUD_WEBHOOK_HEADER || 'Authorization') {
if (!secret) return false; // never accept when unconfigured
const received = headers[headerName.toLowerCase()]; // whole value, nothing stripped
return typeof received === 'string' && received !== '' && safeEqual(received, secret);
}
- The header name is whatever you put in
authData({"Authorization": "Bearer abc"}), so read it fromNEXTCLOUD_WEBHOOK_HEADER(defaultAuthorization). Look it up case-insensitively. NEXTCLOUD_WEBHOOK_SECRETis the exact, whole header value, including anyBearerprefix you typed. Compare the whole value; don't strip or parse a prefix.- Return
401for a missing header or mismatch,400for a non-JSON body,200once handled. - Read the body raw (
express.raw({ type: '*/*' })) and check the credential before parsing it. - Python:
hmac.compare_digeston bytes (encode both sides;strraises on non-ASCII).
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Security: a static shared credential authenticates the sender, not the body, and gives no replay protection (no signature, no timestamp). Serve the endpoint over HTTPS and rotate the credential if it leaks.
The HTTP Method Is Not Fixed
httpMethod is a free string set when the webhook is registered, so the route must accept POST, PUT, PATCH, GET and DELETE, and cope with an empty body. The examples do this (Next.js exports a handler for each method). If you receive through Hookdeck: the Nextcloud source pre-selects POST without managing the method, so any other method your listener uses must also be allowed on the source.
Event Types
The body is { "event": { "class": ..., ... }, "user": { "uid", "displayName" }, "time": <unix> }. event.class is the fully-qualified PHP class name (user is null when no session exists).
event.class | Sent When |
|---|---|
OCP\Files\Events\Node\NodeCreatedEvent | A file or folder is created |
OCP\Files\Events\Node\NodeWrittenEvent | A file is written |
OCP\Files\Events\Node\NodeTouchedEvent | A node is touched |
OCP\Files\Events\Node\NodeDeletedEvent | A node is deleted (node has path but usually no id) |
OCP\Files\Events\Node\NodeCopiedEvent / NodeRenamedEvent / NodeRestoredEvent | Two-node events with event.source and event.target |
OCP\SystemTag\TagAssignedEvent / TagUnassignedEvent | A system tag is (un)assigned |
OCP\Calendar\Events\CalendarObjectCreatedEvent | A calendar object is created (also Updated, Deleted, MovedToTrash, Restored, Moved) |
OCA\Forms\Events\FormSubmittedEvent | A form is submitted |
Before* node events, Tables (OCA\Tables\Event\Row*Event) and Mail events exist too. Apps can register more, so see references/overview.md.
Environment Variables
NEXTCLOUD_WEBHOOK_HEADER=Authorization # header name from authData
NEXTCLOUD_WEBHOOK_SECRET=Bearer your-shared-secret # exact, whole header value
Local Development
Nextcloud must be able to reach your endpoint. Tunnel with the Hookdeck CLI (no account required):
npx hookdeck-cli listen 3000 nextcloud --path /webhooks/nextcloud
Use the printed URL as the uri when registering the webhook.
Resources
- references/overview.md - What webhook_listeners sends, payload envelope, events
- references/setup.md - Register a listener with the OCS API and a secret header
- references/verification.md - Header check details, gotchas, debugging 401s
- examples/ - Runnable Express, Next.js and FastAPI handlers with tests