This app allows a Nextcloud instance to notify external systems via HTTP POST requests whenever an event of a given type occurs.
Features:
- Sending webhook notifications to URLs specified on per-event type basis (11 event types supported as of the current version)
- Authenticating outgoing POST requests with SHA256 signatures
- Sending webhook notifications as a Flow action
- Outgoing requests are sent in a fire-and-forget (
exec(curl &)
) manner in order not to block the thread execution
- Nextcloud version 25-26
- Ability to
exec(curl)
from a PHP script
This app is published in the Nextcloud App Store. Alternatively, it can be installed manually by grabbing the release .tar.gz
archive, unpacking it in the apps
folder of your Nextcloud instance and activating the app in the Admin UI.
When active, the App status is reported in Settings > Administration > Security > (scroll down to) Webhooks.
In order to enable webhooks for a given event type, you have to provide the target URL with the config key corresponding the a given event in your config file. Example (User Logged In Event):
'webhooks_user_logged_in_url' => 'https://your-service.tld/hooks/user-logged-in',
Aside from listening for specific events, this app also supports sending HTTP POST notifications triggered by a Flow defined by the admin with a specified endpoint URL.
With the example Flow listed above being active, when a new a new PDF file is uploaded, name of which matches report, a POST request is sent to the specified URL with the following payload:
{
eventType: 'OCA\\WorkflowEngine\\Entity\\File',
eventName: '\\OCP\\Files::postCreate',
node: {
id: 354,
storage: {
mountPoint: '/admin/',
cache: null,
scanner: {},
watcher: null,
propagator: null,
updater: {}
},
path: '/admin/files/report-1234.pdf',
internalPath: 'files/report-1234.pdf',
modifiedTime: 1631793637,
size: 1642,
Etag: '6a8a183a68f22455d7a561d8e3d1f6b9',
mimeType: 'application/pdf',
permissions: 27,
isUpdateable: true,
isDeletable: true,
isShareable: true
},
workflowFile: {
displayText: 'admin created report-1234.pdf',
url: 'http://localhost:8080/index.php/f/354'
}
}
Example notification payloads for other Flow event types are listed here.
If the Nextcloud instance and the service responsible for receiving incoming webhook notifications are to communicate over public internet, it is important to provide a secret key used for signing the notifications in order to protect the receiving service from spoofing attacks. This app allows you to define webhooks_secret
in your Nextcloud config.php
like so:
'webhooks_secret' => 'yoursecret1234',
Once the secret is defined, all outgoing webhook notifications will contain a signature in the X-Nextcloud-Webhooks
HTTP header. The signature is calculated by performing a SHA256 function on the POST request raw body concatenated with the secret defined earlier.
Below is a minimal example of a Node.js Express app (with body-parser
) validating incoming webhook notification signature:
const express = require("express");
const app = express();
const bodyParser = require('body-parser');
const crypto = require('crypto');
app.use(bodyParser.json({
verify: function(req, res, buf, encoding) {
req.rawBody = buf.toString();
}
}))
app.post('/login-failed', (req, res) => {
var hash = crypto.createHash('sha256');
hash.update(req.rawBody + "yoursecret1234");
var expected = hash.digest('hex');
var obtained = req.get('X-Nextcloud-Webhooks');
console.log("expected: ", expected);
console.log("obtained: ", obtained);
if (expected === obtained) {
// request signature is VALID
console.log(req.body);
} else {
// request signature is INVALID
}
res.status(200);
})
app.listen(3000, () => { console.log("Server started.") })
Fires whenever a calendar event is created.
Config name: webhooks_calendar_object_created_url
Notification payload:
{
calendarId: 2,
calendarData: {
id: '2',
uri: 'personal',
principaluri: 'principals/users/admin',
// [...]
'{http://apple.com/ns/ical/}calendar-order': '0',
'{http://apple.com/ns/ical/}calendar-color': '#795AAB',
'{http://nextcloud.com/ns}deleted-at': null,
'{http://nextcloud.com/ns}owner-displayname': 'admin'
},
shares: [],
objectData: {
id: '1',
uri: '399C189E-CCA6-4F67-AC67-35A9B53B51EB.ics',
lastmodified: '1633198361',
etag: '"58731caeaf6c998ca2dbcf5d83af756a"',
calendarid: '2',
size: 901,
calendardata: 'BEGIN:VCALENDAR\r\n' +
'VERSION:2.0\r\n' +
'CALSCALE:GREGORIAN\r\n' +
'PRODID:-//IDN nextcloud.com//Calendar app 2.2.2//EN\r\n' +
'BEGIN:VTIMEZONE\r\n' +
// [...] more data in iCal format
'END:VEVENT\r\n' +
'END:VCALENDAR\r\n',
component: 'vevent',
classification: 0
},
eventType: 'OCA\\DAV\\Events\\CalendarObjectCreatedEvent'
}
Fires whenever a calendar event is edited (including meeting participant accepting/declining invitations).
Config name: webhooks_calendar_object_updated_url
Notification payload:
{
calendarId: 2,
calendarData: {
id: '2',
uri: 'personal',
principaluri: 'principals/users/admin',
// [...]
'{http://apple.com/ns/ical/}calendar-order': '0',
'{http://apple.com/ns/ical/}calendar-color': '#795AAB',
'{http://nextcloud.com/ns}deleted-at': null,
'{http://nextcloud.com/ns}owner-displayname': 'admin'
},
shares: [],
objectData: {
id: '1',
uri: '399C189E-CCA6-4F67-AC67-35A9B53B51EB.ics',
lastmodified: '1633198361',
etag: '"58731caeaf6c998ca2dbcf5d83af756a"',
calendarid: '2',
size: 901,
calendardata: 'BEGIN:VCALENDAR\r\n' +
'VERSION:2.0\r\n' +
'CALSCALE:GREGORIAN\r\n' +
'PRODID:-//IDN nextcloud.com//Calendar app 2.2.2//EN\r\n' +
'BEGIN:VTIMEZONE\r\n' +
// [...] more data in iCal format
'END:VEVENT\r\n' +
'END:VCALENDAR\r\n',
component: 'vevent',
classification: 0
},
eventType: 'OCA\\DAV\\Events\\CalendarObjectUpdatedEvent'
}
Fires whenever a calendar event is soft-deleted (moved to trash).
Config name: webhooks_calendar_object_moved_to_trash_url
Notification payload:
{
calendarId: 30,
calendarData: {
id: '2',
uri: 'personal',
principaluri: 'principals/users/admin',
// [...]
'{http://apple.com/ns/ical/}calendar-order': '0',
'{http://apple.com/ns/ical/}calendar-color': '#795AAB',
'{http://nextcloud.com/ns}deleted-at': null,
'{http://nextcloud.com/ns}owner-displayname': 'admin'
},
shares: [],
objectData: {
id: '118',
uri: 'E855AF90-4A9B-4223-95E1-1FE700C4BDC0.ics',
lastmodified: '1660572955',
etag: '"79c725344d04a73b9bad1addee500bcf"',
calendarid: '30',
size: 748,
calendardata: 'BEGIN:VCALENDAR\r\n' +
'CALSCALE:GREGORIAN\r\n' +
'VERSION:2.0\r\n' +
'PRODID:-//IDN nextcloud.com//Calendar app 3.3.1//EN\r\n' +
'BEGIN:VEVENT\r\n' +
// [...] more data in iCal format
'END:VTIMEZONE\r\n' +
'END:VCALENDAR',
component: 'vevent',
classification: 0
},
eventType: 'OCA\\DAV\\Events\\CalendarObjectMovedToTrashEvent'
}
Fires whenever a calendar event is permanently deleted (i.e. deleted from the trashbin).
Config name: webhooks_calendar_object_deleted
Notification payload:
{
calendarId: 30,
calendarData: {
id: '30',
uri: 'test',
principaluri: 'principals/users/admin',
// [...]
'{http://apple.com/ns/ical/}calendar-order': 0,
'{http://apple.com/ns/ical/}calendar-color': '#499AA2',
'{http://nextcloud.com/ns}deleted-at': null,
'{http://nextcloud.com/ns}owner-displayname': 'admin'
},
shares: [],
objectData: {
id: '118',
uri: 'E855AF90-4A9B-4223-95E1-1FE700C4BDC0-deleted.ics',
lastmodified: '1660572955',
etag: '"79c725344d04a73b9bad1addee500bcf"',
calendarid: '30',
size: 748,
calendardata: 'BEGIN:VCALENDAR\r\n' +
'CALSCALE:GREGORIAN\r\n' +
'VERSION:2.0\r\n' +
'PRODID:-//IDN nextcloud.com//Calendar app 3.3.1//EN\r\n' +
'BEGIN:VEVENT\r\n' +
// [...] more data in iCal format
'END:VTIMEZONE\r\n' +
'END:VCALENDAR',
component: 'vevent',
classification: 0
},
eventType: 'OCA\\DAV\\Events\\CalendarObjectDeletedEvent'
}
Fires whenever a login attempt with an existing username fails.
Config name: webhooks_login_failed_url
Notification payload:
{
userId: 'admin',
eventType: 'OCP\\Authentication\\Events\\LoginFailedEvent'
}
Fires whenever user's password is changed.
Config name: webhooks_password_updated_url
Notification payload:
{
user: {
id: 'jdoe',
displayName: 'John Doe',
lastLogin: 0,
home: '/home/nextcloud/data/jdoe',
emailAddress: '[email protected]',
cloudId: '[email protected]',
quota: '5 GB'
},
eventType: 'OCP\\User\\Events\\PasswordUpdatedEvent'
}
Fires whenever a new share is created.
Config name: webhooks_share_created_url
Notification payload:
{
id: '1',
fullId: 'ocinternal:1',
nodeId: 7,
nodeType: 'file',
shareType: 3,
sharedWith: null,
sharedWithDisplayName: null,
sharedWithAvatar: null,
permissions: 17,
status: 0,
note: '',
expirationDate: null,
label: '',
sharedBy: 'admin',
shareOwner: 'admin',
token: '7qSPknbEjeHAzgJ',
target: '/welcome.txt',
shareTime: {
date: '2021-06-20 14:23:18.000000',
timeinzone_type: 3,
timezone: 'UTC'
},
mailSend: true,
hideDownload: false,
eventType: 'OCP\\Share\\Events\\ShareCreatedEvent'
}
Fires whenever a user account is edited. Includes values before and after edit.
Config name: webhooks_user_changed_url
Notification payload:
{
user: {
id: 'jdoe',
displayName: 'John Doe',
lastLogin: 0,
home: '/home/nextcloud/data/jdoe',
emailAddress: '[email protected]',
cloudId: '[email protected]',
quota: '5 GB'
},
feature: 'quota',
value: '5 GB',
oldValue: 'default',
eventType: 'OCP\\User\\Events\\UserChangedEvent'
}
Fires whenever a new user is created.
Config name: webhooks_user_created_url
Notification payload:
{
user: {
id: 'admin',
displayName: 'Jane Doe',
lastLogin: 1624203500,
home: '/home/nextcloud/data/admin',
emailAddress: null,
cloudId: '[email protected]',
quota: 'none'
},
loginName: 'admin',
isTokenLogin: false,
eventType: 'OCP\\User\\Events\\UserLoggedInEvent'
}
Fires whenever a user account is deleted.
Config name: webhooks_user_deleted_url
Notification payload:
{
user: {
id: 'jdoe',
displayName: 'John Doe',
lastLogin: 0,
home: '/home/nextcloud/data/jdoe',
emailAddress: null,
cloudId: '[email protected]',
quota: 'none'
},
eventType: 'OCP\\User\\Events\\UserDeletedEvent'
}
Fires whenever user's live status is updated to online (happens when user navigates between apps and doesn't necessarily indicate that the status changed).
Config name: webhooks_user_status_url
{
user: {
id: 'admin',
displayName: 'Jane Doe',
lastLogin: 1631198561,
home: '/home/nextcloud/data/admin',
emailAddress: null,
cloudId: '[email protected]',
quota: 'none'
},
status: 'online',
timestamp: 1633198561,
eventType: 'OCP\\User\\Events\\UserLiveStatusEvent'
}
Fires whenever a user logs in successfully.
Config name: webhooks_user_logged_in_url
Notification payload:
{
user: {
id: 'admin',
displayName: 'Jane Doe',
lastLogin: 1624203500,
home: '/home/nextcloud/data/admin',
emailAddress: null,
cloudId: '[email protected]',
quota: 'none'
},
loginName: 'admin',
isTokenLogin: false,
eventType: 'OCP\\User\\Events\\UserLoggedInEvent'
}
Fires whenever a user logs out successfully.
Config name: webhooks_user_logged_out_url
Notification payload:
{
user: {
id: 'admin',
displayName: 'Jane Doe',
lastLogin: '1624203500',
home: '/home/nextcloud/data/admin',
emailAddress: null,
cloudId: '[email protected]',
quota: 'none'
},
eventType: 'OCP\\User\\Events\\UserLoggedOutEvent'
}
{
eventType: 'OCA\\WorkflowEngine\\Entity\\File',
eventName: '\\OCP\\Files::postCreate',
node: {
id: 75,
storage: {
mountPoint: '/admin/',
cache: null,
scanner: {},
watcher: null,
propagator: null,
updater: {}
},
path: '/admin/files/test2.pdf',
internalPath: 'files/test2.pdf',
modifiedTime: 1633196032,
size: 1642,
Etag: '97e0c8f490acb83e9b94f9c9e8eeb280',
mimeType: 'application/pdf',
permissions: 27,
isUpdateable: true,
isDeletable: true,
isShareable: true
},
workflowFile: {
displayText: 'admin created test2.pdf',
url: 'http://localhost:8000/index.php/f/75'
}
}
{
eventType: 'OCA\\WorkflowEngine\\Entity\\File',
eventName: '\\OCP\\Files::postWrite',
node: {
id: 128,
storage: {
cache: null,
scanner: {},
watcher: null,
propagator: null,
updater: {}
},
path: '/admin/files/test-file.txt',
internalPath: 'files/test-file.txt',
modifiedTime: 1647687121,
size: 9,
Etag: '914a2364bc8786dcb2a3b74889f692f5',
mimeType: 'text/plain',
permissions: 27,
isUpdateable: true,
isDeletable: true,
isShareable: true
},
workflowFile: {
displayText: 'admin modified test-file.txt',
url: 'http://localhost:8000/index.php/f/128'
}
}
{
eventType: 'OCA\\WorkflowEngine\\Entity\\File',
eventName: '\\OCP\\Files::postRename',
node: {
id: 75,
storage: {
mountPoint: '/admin/',
cache: null,
scanner: {},
watcher: null,
propagator: null,
updater: {}
},
path: '/admin/files/test2.pdf',
internalPath: 'files/test2.pdf',
modifiedTime: 1633196032,
size: 1642,
Etag: '97e0c8f490acb83e9b94f9c9e8eeb280',
mimeType: 'application/pdf',
permissions: 27,
isUpdateable: true,
isDeletable: true,
isShareable: true
},
workflowFile: {
displayText: 'admin renamed test2.pdf',
url: 'http://localhost:8000/index.php/f/75'
}
}
{
eventType: 'OCA\\WorkflowEngine\\Entity\\File',
eventName: '\\OCP\\Files::postCopy',
node: {
id: 80,
storage: {
mountPoint: '/admin/',
cache: null,
scanner: {},
watcher: null,
propagator: null,
updater: {}
},
path: '/admin/files/test2 (copy).pdf',
internalPath: 'files/test2 (copy).pdf',
modifiedTime: 1647686658,
size: 1642,
Etag: '3ce44278e289299fa8e6a27f05b7f54e',
mimeType: 'application/pdf',
permissions: 27,
isUpdateable: true,
isDeletable: true,
isShareable: true
},
workflowFile: {
displayText: 'admin copied test2 (copy).pdf',
url: 'http://localhost:8000/index.php/f/80'
}
}
{
eventType: 'OCA\\WorkflowEngine\\Entity\\File',
eventName: '\\OCP\\Files::postDelete',
node: {
id: 75,
storage: {
mountPoint: '/admin/',
cache: null,
scanner: {},
watcher: null,
propagator: null,
updater: {}
},
path: '/admin/files/test2.pdf',
internalPath: 'files/test2.pdf',
modifiedTime: 1633196032,
size: 1642,
Etag: '97e0c8f490acb83e9b94f9c9e8eeb280',
mimeType: 'application/pdf',
permissions: 27,
isUpdateable: true,
isDeletable: true,
isShareable: true
},
workflowFile: {
displayText: 'admin deleted test2.pdf',
url: 'http://localhost:8000/index.php/f/75'
}
}
{
eventType: 'OCA\\WorkflowEngine\\Entity\\File',
eventName: 'OCP\\SystemTag\\ISystemTagObjectMapper::assignTags',
mapperEvent: {
eventName: 'OCP\\SystemTag\\ISystemTagObjectMapper::assignTags',
objectType: 'files',
objectId: '75',
tags: [ '1' ]
},
workflowFile: {
displayText: 'admin assigned test-tag to test2.pdf',
url: 'http://localhost:8000/index.php/f/75'
}
}