Der Benutzer (Administrator) hat die Möglichkeit, die Übermittlung und Struktur von Ereignissen im QRmaint-System für den Empfänger-Webhook zu konfigurieren.

Um einen neuen Webhook einzurichten, geben Sie bitte folgende Daten ein:
- Ereignistyp
- URL der Client-API
- Bezeichnung des Webhooks (optional)
- Geheimnis (secret) – eine Zeichenfolge, die die Kommunikation zwischen QRmaint und der Client-Anwendung sichert. Es wird empfohlen, ein Geheimnis festzulegen.

Jede angelegte Webhook-Konfiguration kann deaktiviert, bearbeitet oder gelöscht werden.
Zusätzlich können Sie ein Test-Webhooks-Ereignis auslösen, mit dem ein reales Ereignis des ausgewählten Typs simuliert wird, jedoch mit fiktiven Testwerten (z. B. einer fiktiven Aufgaben-ID). Dies erleichtert die Einrichtung Ihrer Client-Server-Anwendung für die Verarbeitung von QRmaint-Webhooks.
Derselbe Ereignistyp kann in mehreren Webhooks verwendet werden, sodass Clients verschiedene Szenarien zur Verarbeitung eines QRmaint-Ereignisses erstellen können.

Für jedes registrierte Ereignis in einem Webhook gibt es einen Aufrufverlauf mit zugehörigem Status.
Der Status zeigt an, ob das Ereignis von der Client-Server-Anwendung korrekt verarbeitet wurde.
Ein Testereignis wird durch das TEST-Tag gekennzeichnet.
Der Aufrufverlauf enthält außerdem die eindeutige Ereignis-ID, den Nachrichteninhalt sowie die Uhrzeit der Ereignisaufzeichnung.

Zusätzliche Informationen für Entwickler #
Die Kommunikation von QRmaint-Webhooks mit einer externen API erfolgt über HTTP nach aktuellen Standards.
Webhook-HTTP-Anfragen von QRmaint werden mit der POST-Methode gesendet.
Jede HTTP-Anfrage enthält einen Nachrichteninhalt, der im HTTP-Standard als Body bezeichnet wird.
In QRmaint wird dieser Body in strukturierter Form (Payload) übertragen. Die Struktur hängt vom jeweiligen Ereignistyp des Webhooks ab.
Beispiel:
Payload, der durch ein Ereignis zur Statusänderung einer Aufgabe generiert wird:
{
string EventTypeId,
string RequestId
}
sowie Felder, die vom Kontext abhängen (Ereignistyp des Webhooks)
{
int WorkId,
int? PreviousStatusId,
int? NewStatusId
}
Beispiel für den Body einer Anfrage, die durch ein Ereignis zur Statusänderung einer Aufgabe generiert wurde:
{
"EventTypeId": "WORK_STATUS_CHANGED",
"WorkId": 1371172,
"PreviousStatusId": 690,
"NewStatusId": 691,
"RequestId": "d8f2be95-55b6-4578-9c09-a085b02201aa"
}
Sicherheit und Geheimnis #
Der Inhalt der Anfrage wird mit dem HMAC-SHA256-Algorithmus (Hash Message Authentication Code) gehasht, unter Verwendung einer Zeichenfolge – des Geheimnisses.
Das Geheimnis wird auf der Seite des QRmaint-Systems in der Hash-Funktion verwendet, muss jedoch auch in der Client-Anwendung gespeichert werden, um die Korrektheit der empfangenen Anfrage prüfen zu können. Jede Webhook-Ereignis-Konfiguration sollte ein eigenes, einzigartiges Geheimnis haben.
Das QRmaint-System verwendet das Geheimnis zusammen mit dem HTTP-Request-Body (als Zeichenfolge / String), um einen Hash zu erzeugen, der im Header x-qrmaint-signature übertragen wird.
Die Client-Server-Anwendung sollte den Header der Anfrage auslesen und anschließend den HMAC-SHA256-Algorithmus unter Verwendung des Geheimnisses und des Request-Bodys anwenden, um zu prüfen, ob der aus dem Header ausgelesene Wert mit dem berechneten Hash übereinstimmt.
Wenn die Werte übereinstimmen, bedeutet dies, dass die empfangenen Daten tatsächlich vom QRmaint-System gesendet wurden und nicht z. B. von einem Server, der sich als QRmaint ausgibt.
Beispiel einer Funktion zur Überprüfung der Authentizität von Webhook-Anfragen in JavaScript (Node.js):
function checkHMAC(requestSignature, body, secret) {
const hash = crypto.createHmac('SHA256', secret).update(body).digest('hex');
if (requestSignature == hash) {
console.log('request signature match');
return true;
} else {
console.log('request signature not match');
return false;
}
}
Beispiel für einen einfachen HTTP-Server in Node.js, der Anfragen von QRmaint-Webhooks verarbeiten kann:
const express = require('express');
const crypto = require('crypto');
const https = require('https');
const fs = require('fs');
const path = require('path');
const app = express();
const port = 4000;
const secret = '1234567890';
const bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const options = {
key: fs.readFileSync(path.join(__dirname, './certs/key.pem')),
cert: fs.readFileSync(path.join(__dirname, './certs/cert.pem'))
}
const sslServer = https.createServer(options, app);
function checkHMAC(requestSignature, body, secret) {
const hash = crypto.createHmac('SHA256', secret).update(body).digest('hex');
if (requestSignature == hash) {
console.log('request signature match');
return true;
} else {
console.log('request signature not match');
return false;
}
}
app.post('/webhook-test/new-work-request', (req, res) => {
const body = req.body;
const headers = req.headers;
const requestSignature = headers['x-qrmaint-signature'];
if (checkHMAC(requestSignature, JSON.stringify(body), secret)) {
res.status(201).json({success: true, data: {}});
} else {
res.status(401).json({success: false, data: {}});
}
});
app.post('/webhook-test/new-work-order', (req, res) => {
const body = req.body;
const headers = req.headers;
const requestSignature = headers['x-qrmaint-signature'];
if (checkHMAC(requestSignature, JSON.stringify(body), secret)) {
res.status(201).json({success: true, data: {}});
} else {
res.status(401).json({success: false, data: {}});
}
});
app.post('/webhook-test/work-status-changed', (req, res) => {
const body = req.body;
const headers = req.headers;
const requestSignature = headers['x-qrmaint-signature'];
if (checkHMAC(requestSignature, JSON.stringify(body), secret)) {
res.status(201).json({success: true, data: {}});
} else {
res.status(401).json({success: false, data: {}});
}
});
sslServer.listen(port, () => {
console.log(`Secure server is listening on port ${port}`);
});