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_listeners request? (There is no signature)
  • How do I use authMethod: "header" and authData to add a secret header?
  • How do I handle NodeCreatedEvent, NodeWrittenEvent, NodeDeletedEvent or CalendarObjectCreatedEvent?
  • 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 from NEXTCLOUD_WEBHOOK_HEADER (default Authorization). Look it up case-insensitively.
  • NEXTCLOUD_WEBHOOK_SECRET is the exact, whole header value, including any Bearer prefix you typed. Compare the whole value; don't strip or parse a prefix.
  • Return 401 for a missing header or mismatch, 400 for a non-JSON body, 200 once handled.
  • Read the body raw (express.raw({ type: '*/*' })) and check the credential before parsing it.
  • Python: hmac.compare_digest on bytes (encode both sides; str raises 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.classSent When
OCP\Files\Events\Node\NodeCreatedEventA file or folder is created
OCP\Files\Events\Node\NodeWrittenEventA file is written
OCP\Files\Events\Node\NodeTouchedEventA node is touched
OCP\Files\Events\Node\NodeDeletedEventA node is deleted (node has path but usually no id)
OCP\Files\Events\Node\NodeCopiedEvent / NodeRenamedEvent / NodeRestoredEventTwo-node events with event.source and event.target
OCP\SystemTag\TagAssignedEvent / TagUnassignedEventA system tag is (un)assigned
OCP\Calendar\Events\CalendarObjectCreatedEventA calendar object is created (also Updated, Deleted, MovedToTrash, Restored, Moved)
OCA\Forms\Events\FormSubmittedEventA 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 6, 2026

View on GitHub →