Update to NC32 and webhook app is not working

Hi!

I finally updated my NC instance and for my surprise the webhook app is not working anymore. The webhook app is the Webhooks Version 0.4.3 last updated in Feb 2023.

I have the webhooks urls setup in the config file for the calendar events to fire to n8n when an event is created, deleted, updated… but stopped working. I guess that the app it’s out of updates and it’s no more compatible.

Is there an alternative for this purpose?
thank you in advance for your comment.

I don’t have recommendations for a webhook app. But maybe someone else does.

The only alternative I can see would be to change your n8n workflow so that it pulls the entire calendar every hour (or as often as you like) using CalDAV. This is how calendar clients works and it is a standard feature of Nextcloud, so you would not be dependent on a third-party app.

Thank you for your response.
Actually i just found an alternative to register the webhooks with calendar events:

curl -X POST
-u “user:pass”
-H “Content-Type: application/json”
-H “OCS-APIRequest: true”
https://nextcloud-instance/ocs/v2.php/apps/webhook_listeners/api/v1/webhooks”
-d ‘{
“httpMethod”: “POST”,
“uri”: “https://n8n-instance/webhook/xxxx-xxx-xxx-xxx”,
“event”: “OCP\\Calendar\\Events\\CalendarObjectCreatedEvent”,
“authMethod”: “none”

}’

Type of events:

OCP\Calendar\Events\CalendarObjectMovedToTrashEvent
OCP\Calendar\Events\CalendarObjectCreatedEvent
OCP\Calendar\Events\CalendarObjectUpdatedEvent
OCP\Calendar\Events\CalendarObjectDeletedEvent

Be aware that in this example there’s no authmethod.

Also notice that a cronjob must be executed in order for the webhooks fire.

Sharing the code node after the n8n’s webhook node to separate the events data (Be sure to use Raw Body option in the webhook node):

// =====================================================
// :one: Ler payload binário base64 ou JSON
// =====================================================
let rawData;

if ($input.first().binary && $input.first().binary.data) {
// Payload binário base64
const base64Data = $input.first().binary.data.data;
const buff = Buffer.from(base64Data, ‘base64’);
rawData = buff.toString(‘utf8’);
} else {
// fallback: já é JSON
rawData = $input.first().json;
}

// :two: Parse para JSON
const webhookData = typeof rawData === ‘string’ ? JSON.parse(rawData) : rawData;

// :three: Log para depuração
console.log(‘Payload decodificado:’, JSON.stringify(webhookData, null, 2));

// =====================================================
// :four: Extrair dados principais do webhook
// =====================================================
const ical = webhookData?.event?.objectData?.calendardata;
const eventType = webhookData?.event?.class || “”;
const calendarName = webhookData?.event?.calendarData?.[‘{DAV:}displayname’] || “”;
const userName = webhookData?.user?.displayName || “”;
// EXTRAIR O URI ORIGINAL - ESTA É A INFORMAÇÃO CRÍTICA
const originalUri = webhookData?.event?.objectData?.uri;

if (!ical) {
throw new Error(“Payload inválido: ‘objectData.calendardata’ não encontrado.”);
}

// =====================================================
// :five: Funções auxiliares
// =====================================================

function extractField(fieldName, vevent) {
const regex = new RegExp(${fieldName}(?:;[^:]+)?:([^\r\n]+));
const match = vevent.match(regex);
return match ? match[1].trim() : null;
}

function extractAllAttendees(vevent) {
const matches = […vevent.matchAll(/ATTENDEE(?:;[^:]+)?:([^\r\n]+)/g)];
return matches.map(m => cleanEmail(m[1]));
}

function cleanEmail(email) {
return email ? email.replace(/^mailto:/i, ‘’) : null;
}

function toMysqlDateTime(icalDate) {
if (!icalDate) return null;
icalDate = icalDate.replace(/Z$/, ‘’);
icalDate = icalDate.replace(/TZID=[^:]+:/, ‘’);
if (icalDate.includes(‘T’)) {
const year = icalDate.substring(0, 4);
const month = icalDate.substring(4, 6);
const day = icalDate.substring(6, 8);
const hour = icalDate.substring(9, 11) || ‘00’;
const minute = icalDate.substring(11, 13) || ‘00’;
return ${year}-${month}-${day} ${hour}:${minute}:00;
} else {
const year = icalDate.substring(0, 4);
const month = icalDate.substring(4, 6);
const day = icalDate.substring(6, 8);
return ${year}-${month}-${day} 00:00:00;
}
}

// =====================================================
// :six: Extrair VEVENTs
// =====================================================
const vevents = ical.match(/BEGIN:VEVENT([\s\S]*?)END:VEVENT/g) || ;

const events = vevents.map(v => ({
eventType,
calendarName,
userName,
uid: extractField(“UID”, v),
summary: extractField(“SUMMARY”, v),
description: extractField(“DESCRIPTION”, v),
location: extractField(“LOCATION”, v),
status: extractField(“STATUS”, v),
category: extractField(“CATEGORIES”, v),
start: toMysqlDateTime(extractField(“DTSTART”, v)),
end: toMysqlDateTime(extractField(“DTEND”, v)),
created: toMysqlDateTime(extractField(“CREATED”, v)),
lastModified: toMysqlDateTime(extractField(“LAST-MODIFIED”, v)),
dtstamp: toMysqlDateTime(extractField(“DTSTAMP”, v)),
organizer: cleanEmail(extractField(“ORGANIZER”, v)),
attendees: extractAllAttendees(v),
sequence: extractField(“SEQUENCE”, v),
calendardata: v,
// ADICIONAR O URI ORIGINAL
originalUri: originalUri
}));

// =====================================================
// :seven: Output final
// =====================================================
return events.map(e => ({ json: e }));

This topic was automatically closed 8 days after the last reply. New replies are no longer allowed.